1<refentry id="{@id}"> 2 <refnamediv> 3 <refname>XML_Query2XML</refname> 4 <refpurpose>Generating XML data from SQL queries</refpurpose> 5 </refnamediv> 6 <refsynopsisdiv> 7 8 <author> 9 Lukas Feiler 10 <authorblurb> 11 {@link mailto:lukas.feiler@lukasfeiler.com lukas.feiler@lukasfeiler.com} 12 </authorblurb> 13 </author> 14 <copyright>Copyright 2006 by Lukas Feiler</copyright> 15 </refsynopsisdiv> 16 {@toc} 17 <refsect1 id="{@id intro}"> 18 <title>Introduction</title> 19 <para> 20 XML_Query2XML allows you to transform the records retrieved with one or more 21 SQL SELECT queries into XML data. Very simple to highly complex 22 transformations are supported. Is was written with performance in mind and 23 can handle large amounts of data. No XSLT needed! 24 </para> 25 <para> 26 Both methods {@link XML_Query2XML::getXML()} and {@link XML_Query2XML::getFlatXML()} return an instance 27 of {@link http://www.php.net/manual/en/ref.dom.php#dom.class.domdocument DOMDocument}. 28 The class DOMDocument is provided by {@link http://www.php.net/manual/en/ref.dom.php PHP5's built-in DOM API}. 29 </para> 30 </refsect1> 31 32 <refsect1 id="{@id requirements}"> 33 <title>Requirements</title> 34 <para> 35 XML_Query2XML requires 36 <itemizedlist> 37 <listitem> 38 <emphasis>PHP5</emphasis>: XML_Query2XML heavily uses the new exception handling and object orientation features. 39 </listitem> 40 <listitem> 41 <emphasis>PHP5's built-in DOM API</emphasis> 42 </listitem> 43 <listitem> 44 <emphasis>{@link http://www.php.net/PDO PDO} (PHP5's built-in database abstraction class)</emphasis> 45 <emphasis>{@link http://pear.php.net/package/DB PEAR DB}</emphasis>, 46 <emphasis>{@link http://pear.php.net/package/MDB2 PEAR MDB2}</emphasis> or 47 <emphasis>{@link http://adodb.sourceforge.net ADOdb}</emphasis>. 48 </listitem> 49 </itemizedlist> 50 The following packages are optional: 51 <itemizedlist> 52 <listitem> 53 <emphasis>{@link http://pear.php.net/package/I18N_UnicodeString PEAR I18N_UnicodeString}</emphasis>: 54 this is only required if you want to use XML_Query2XML's ability to 55 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_mapper.9075mapping map SQL identifiers to XML names in accordance with ISO/IEC 9075-14:2005}. 56 </listitem> 57 </itemizedlist> 58 </para> 59 </refsect1> 60 61 <refsect1 id="{@id migration}"> 62 <title>Migrating from v0.6.x and v0.7.x to v1.x.x</title> 63 <para> 64 The release 0.8.0 of XML_Query2XML is not backward compatible! 65 Due to security considerations XML_Query2XML does not use the 66 native function eval() anymore. Therefore 67 <itemizedlist> 68 <listitem> 69 the "!" prefix is not supported anymore; this affects 70 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications}, 71 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_value}, 72 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_idcolumn}, 73 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes} and 74 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql.complexqueryspecification Complex Query Specifications} 75 </listitem> 76 <listitem> 77 the behaviour of {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_condition} was changed 78 </listitem> 79 <listitem> 80 the new callback prefix "#" was added (use it instead of the "!" prefix) 81 </listitem> 82 </itemizedlist> 83 </para> 84 <para> 85 Proposed migration strategy: 86 <itemizedlist> 87 <listitem> 88 Wherever you currently use the "!" prefix, use the new callback prefix "#" instead. 89 The first argument passed to the callback function/method is always the current record ($record). 90 You can supply additional static arguments by placing them within the braces, e.g. 91 'MyClass:myMethod(arg2, arg3)' will result in MyClass:myMethod() being called with the current 92 record as the first, the string 'arg2' as the second and 'arg3' as the third argument. In 93 most cases you will want to put whatever code you used after the "!" prefix into a 94 separate function or static method. That function/method is what you call using the callback prefix "#". 95 </listitem> 96 <listitem> 97 The migration for {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_condition} works 98 similarly. Move the PHP code into a separate function/method and call it using the callback 99 prefix "#". 100 </listitem> 101 </itemizedlist> 102 </para> 103 </refsect1> 104 105 <refsect1 id="{@id query2xml_factory}"> 106 <title>XML_Query2XML::factory()</title> 107 <para> 108 {@link XML_Query2XML::factory() XML_Query2XML::factory}($db) 109 </para> 110 <para> 111 This is the factory method that will return a new instance of {@link XML_Query2XML}. 112 The argument passed to the factory method can be an instance of 113 {@link http://www.php.net/PDO PDO}, 114 {@link http://pear.php.net/package/DB PEAR DB}, 115 {@link http://pear.php.net/package/MDB2 PEAR MDB2}, 116 {@link http://adodb.sourceforge.net ADOdb}, 117 {@link http://pear.php.net/package/Net_LDAP PEAR Net_LDAP}, 118 {@link http://pear.php.net/package/Net_LDAP2 PEAR Net_LDAP2} or 119 any class that extends {@link XML_Query2XML_Driver} 120 </para> 121 <refsect2 id="{@id database_drivers}"> 122 <title>Database Drivers for PDO, PEAR MDB2, PEAR DB, ADOdb</title> 123 <para> 124 XML_Query2XML has drivers for the database abstraction layers PDO, PEAR MDB2, PEAR DB and ADOdb. 125 </para> 126 <para> 127 Using PDO with XML_Query2XML works like this: 128 <programlisting role="php"> 129 <![CDATA[ 130<?php 131require_once 'XML/Query2XML.php'; 132$pdo = new PDO('mysql://root@localhost/Query2XML_Tests'); 133$query2xml = XML_Query2XML::factory($pdo); 134?> 135 ]]> 136 </programlisting> 137 </para> 138 <para> 139 Using MDB2 with XML_Query2XML works like this: 140 <programlisting role="php"> 141 <![CDATA[ 142<?php 143require_once 'XML/Query2XML.php'; 144require_once 'MDB2.php'; 145$mdb2 = MDB2::factory('mysql://root@localhost/Query2XML_Tests'); 146$query2xml = XML_Query2XML::factory($mdb2); 147?> 148 ]]> 149 </programlisting> 150 </para> 151 <para> 152 The same thing with PEAR DB looks like that: 153 <programlisting role="php"> 154 <![CDATA[ 155<?php 156require_once 'XML/Query2XML.php'; 157require_once 'DB.php'; 158$db = DB::connect('mysql://root@localhost/Query2XML_Tests'); 159$query2xml = XML_Query2XML::factory($db); 160?> 161 ]]> 162 </programlisting> 163 </para> 164 <para> 165 And again the same thing with ADOdb: 166 <programlisting role="php"> 167 <![CDATA[ 168<?php 169require_once 'XML/Query2XML.php'; 170require_once 'adodb/adodb.inc.php'; 171//require_once 'adodb/adodb-exceptions.inc.php'; 172//require_once 'adodb/adodb-pear.inc.php'; 173$adodb = ADONewConnection('mysql'); 174$adodb->Connect('localhost', 'root', '', 'Query2XML_Tests'); 175$query2xml = XML_Query2XML::factory($adodb); 176?> 177 ]]> 178 </programlisting> 179 Note that XML_Query2XML works with ADOdb with the default error handling (no additional include file), 180 error handling using exceptions (adodb-exceptions.inc.php) and error handling using PEAR_Error 181 (adodb-pear.inc.php). 182 </para> 183 <para> 184 I would recommend using MDB2 as it can be considered more advanced than DB 185 and much better designed and documented than ADOdb. MDB2 also provides more 186 flexibility than PDO. If you want to access a SQLite 3 database use PDO - MDB2 187 does only support SQLite 2 as of this writing. 188 But use whichever you like - XML_Query2XML works with all of them. 189 For the sake of simplicity all the examples will use PEAR MDB2. 190 </para> 191 </refsect2> 192 <refsect2 id="{@id ldap_driver}"> 193 <title>LDAP Driver for PEAR Net_LDAP</title> 194 <para> 195 Since v1.6.0RC1 XML_Query2XML comes with a driver for {@link http://pear.php.net/package/Net_LDAP PEAR Net_LDAP}. 196 The driver for {@link http://pear.php.net/package/Net_LDAP2 PEAR Net_LDAP2} is available since v1.7.0RC1. 197 </para> 198 <para> 199 Using Net_LDAP(2) with XML_Query2XML works like this: 200 <programlisting role="php"> 201 <![CDATA[ 202<?php 203require_once 'XML/Query2XML.php'; 204$ldap = Net_LDAP::connect( 205 'host' => 'ldap.example.com', 206 'port' => 389, 207 'version' => 3, 208 'starttls' => true, 209 'binddn' => 'cn=Manager,ou=people,dc=example,dc=com', 210 'bindpw' => 'secret' 211); 212$query2xml = XML_Query2XML::factory($ldap); 213?> 214 ]]> 215 </programlisting> 216 The driver for Net_LDAP(2) uses a diffrent format for 217 {@tutorial XML_Query2XML.pkg#query2xml_getxml.sql}. Instead of 218 a string it expects an associative array with the following elements: 219 <itemizedlist> 220 <listitem> 221 <emphasis>'base'</emphasis>: the base search DN 222 </listitem> 223 <listitem> 224 <emphasis>'filter'</emphasis>: the query filter that determines which results are returned 225 </listitem> 226 <listitem> 227 <emphasis>'options'</emphasis>: an array of configuration options for the current query 228 </listitem> 229 </itemizedlist> 230 More information on how to use the LDAP drivers can be found under 231 {@tutorial XML_Query2XML.pkg#ldap} 232 </para> 233 </refsect2> 234 </refsect1> 235 236 <refsect1 id="{@id query2xml_getflatxml}"> 237 <title>XML_Query2XML::getFlatXML()</title> 238 <para> 239 {@link XML_Query2XML::getFlatXML() XML_Query2XML::getFlatXML}($sql, $rootTagName = 'root', $rowTagName = 'row') 240 </para> 241 <para> 242 This method transforms the data retrieved by a single SQL query into flat XML data. Pass the SQL SELECT statement 243 as first, the root tag's name as second and the row tag's name as third argument. 244 </para> 245 <para> 246 In most cases you will want to use {@tutorial XML_Query2XML.pkg#query2xml_getxml} instead. 247 Please see {@tutorial XML_Query2XML.pkg#casestudies.case01} for an example usage of getFlatXML(). 248 </para> 249 </refsect1> 250 251 <refsect1 id="{@id query2xml_getxml}"> 252 <title>XML_Query2XML::getXML()</title> 253 <para> 254 {@link XML_Query2XML::getXML() XML_Query2XML::getXML}($sql, $options) 255 </para> 256 <para> 257 This method is the most powerful transformation method. It returns an instance of 258 DOMDocument (part of PHP5's built-in DOM API). The records returned by the query/queries will be processed 259 one after another. The $options argument is a rather complex, associative, 260 multi dimensional array. The $sql argument can be a string or as well an associative array. 261 </para> 262 263 <refsect2 id="{@id sql}"> 264 <title>$sql</title> 265 <para> 266 This option is almost exactly like {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql}: you 267 can specify the query with a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql.simplequeryspecification} 268 or a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql.complexqueryspecification}. 269 What is different from {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql} is that 270 you can also specify a boolean value of false. 271 </para> 272 <para> 273 Here is an example of a simple query specification (<emphasis>WARNING:</emphasis> to prevent SQL injection 274 vulerabilities you should use a complex query specification when dealing with non-static queries like this one): 275 <programlisting role="php"> 276 <![CDATA[ 277<?php 278require_once 'XML/Query2XML.php'; 279require_once 'MDB2.php'; 280$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 281if (isset($_REQUEST['artistid']) && is_numeric($_REQUEST['artistid'])) { 282 $artistid = $_REQUEST['artistid']; 283} else { 284 $artistid = 1; 285} 286$dom = $query2xml->getXML( 287 "SELECT * FROM artist WHERE artistid = $artistid", 288 array( 289 'rootTag' => 'favorite_artist', 290 'idColumn' => 'artistid', 291 'rowTag' => 'artist', 292 'elements' => array( 293 'name', 294 'birth_year', 295 'music_genre' => 'genre' 296 ) 297 ) 298); 299header('Content-Type: application/xml'); 300$dom->formatOutput = true; 301print $dom->saveXML(); 302?> 303 ]]> 304 </programlisting> 305 With simple query specifications you have to prevent SQL injection yourself. Here I ensured 306 that $artistid is numeric by calling is_numeric(). 307 </para> 308 <para> 309 Next we use a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql.complexqueryspecification} 310 and prevent SQL injections by using PDO's/MDB2's/DB's/ADOdb's prepare() and execute() methods. 311 <programlisting role="php"> 312 <![CDATA[ 313<?php 314require_once 'XML/Query2XML.php'; 315require_once 'MDB2.php'; 316$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 317$artistid = $_REQUEST['artistid']; 318$dom = $query2xml->getXML( 319 array( 320 'data' => array( 321 ":$artistid" 322 ), 323 'query' => 'SELECT * FROM artist WHERE artistid = ?' 324 ), 325 array( 326 'rootTag' => 'favorite_artist', 327 'idColumn' => 'artistid', 328 'rowTag' => 'artist', 329 'elements' => array( 330 'name', 331 'birth_year', 332 'music_genre' => 'genre' 333 ) 334 ) 335); 336header('Content-Type: application/xml'); 337$dom->formatOutput = true; 338print $dom->saveXML(); 339?> 340 ]]> 341 </programlisting> 342 The resulting XML data is identical in both cases (given that artistid was submitted as 1): 343 <programlisting role="tutorial"> 344 <![CDATA[ 345<?xml version="1.0" encoding="UTF-8"?> 346<favorite_artist> 347 <artist> 348 <name>Curtis Mayfield</name> 349 <birth_year>1920</birth_year> 350 <music_genre>Soul</music_genre> 351 </artist> 352</favorite_artist> 353 ]]> 354 </programlisting> 355 </para> 356 <para> 357 As stated above $sql can also be a boolean value of false. This will only be useful in 358 scenarios where you want to combine the results of multiple unrelated queries into 359 a single XML document. XML_Query2XML will deal with an $sql argument that has a value 360 of false as if it executed a query that returned a single record with no colunns. 361 </para> 362 <para> 363 If you simpy wanted all the records of the table "album" and all 364 the records of the table "artist" you could write code like this: 365 <programlisting role="php"> 366 <![CDATA[ 367<?php 368require_once 'XML/Query2XML.php'; 369require_once 'MDB2.php'; 370 371$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 372 373$dom = $query2xml->getXML( 374 false, 375 array( 376 'idColumn' => false, 377 'rowTag' => '__tables', 378 'rootTag' => 'music_store', 379 'elements' => array( 380 'artists' => array( 381 'rootTag' => 'artists', 382 'rowTag' => 'artist', 383 'idColumn' => 'artistid', 384 'sql' => 'SELECT * FROM artist', 385 'elements' => array( 386 '*' 387 ) 388 ), 389 'albums' => array( 390 'rootTag' => 'albums', 391 'rowTag' => 'album', 392 'idColumn' => 'albumid', 393 'sql' => 'SELECT * FROM album', 394 'elements' => array( 395 '*' 396 ) 397 ) 398 ) 399 ) 400); 401 402header('Content-Type: application/xml'); 403$dom->formatOutput = true; 404print $dom->saveXML(); 405?> 406 ]]> 407 </programlisting> 408 In this case we actually are not interested in $sql at all; all we want is to get our 409 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql}s executed. 410 Also note that we used '__tables' for {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_rowtag} 411 at the root level: this is because we don't have anything to loop over at the root level - remember 412 using false for $sql is like using a query that returns a single record with no columns. 413 </para> 414 <para> 415 The resulting XML looks like this: 416 <programlisting role="tutorial"> 417 <![CDATA[ 418<?xml version="1.0" encoding="UTF-8"?> 419<music_store> 420 <artists> 421 <artist> 422 <artistid>1</artistid> 423 <name>Curtis Mayfield</name> 424 <birth_year>1920</birth_year> 425 <birth_place>Chicago</birth_place> 426 <genre>Soul</genre> 427 </artist> 428 <artist> 429 <artistid>2</artistid> 430 <name>Isaac Hayes</name> 431 <birth_year>1942</birth_year> 432 <birth_place>Tennessee</birth_place> 433 <genre>Soul</genre> 434 </artist> 435 <artist> 436 <artistid>3</artistid> 437 <name>Ray Charles</name> 438 <birth_year>1930</birth_year> 439 <birth_place>Mississippi</birth_place> 440 <genre>Country and Soul</genre> 441 </artist> 442 </artists> 443 <albums> 444 <album> 445 <albumid>1</albumid> 446 <artist_id>1</artist_id> 447 <title>New World Order</title> 448 <published_year>1990</published_year> 449 <comment>the best ever!</comment> 450 </album> 451 <album> 452 <albumid>2</albumid> 453 <artist_id>1</artist_id> 454 <title>Curtis</title> 455 <published_year>1970</published_year> 456 <comment>that man's got somthin' to say</comment> 457 </album> 458 <album> 459 <albumid>3</albumid> 460 <artist_id>2</artist_id> 461 <title>Shaft</title> 462 <published_year>1972</published_year> 463 <comment>he's the man</comment> 464 </album> 465 </albums> 466</music_store> 467 ]]> 468 </programlisting> 469 </para> 470 <para> 471 Want to dump not just two but all of your table? Have a look at 472 {@tutorial XML_Query2XML.pkg#dumpdatabase}. 473 </para> 474 </refsect2> 475 476 <refsect2 id="{@id options_elements}"> 477 <title>$options['elements']</title> 478 <para> 479 This option is an array that basically holds column names to include in the XML data as child 480 elements. There are two types of element specifications: 481 <itemizedlist> 482 <listitem> 483 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications} 484 </listitem> 485 <listitem> 486 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.complex_element_specifications} 487 </listitem> 488 </itemizedlist> 489 </para> 490 <refsect3 id="{@id simple_element_specifications}"> 491 <title>Simple Element Specifications</title> 492 <para> 493 These allow you to use an array to specify elements that have only two properties: a name and a value. 494 The array values are used to specify the XML element values whereas the array keys are used to specify the 495 XML element names. For all array elements that are defined without a key, the array values will be used for 496 the XML element names. If no prefix is used (see below) the contents of the array values are interpreted 497 as column names. The following example illustrates the most basic usage of a simple element specification: 498 <programlisting role="php"> 499 <![CDATA[ 500array( 501 'COLUMN1', 502 'COLUMN2' 503); 504 ]]> 505 </programlisting> 506 This might result in XML data like this: 507 <programlisting role="tutorial"> 508 <![CDATA[ 509<COLUMN1>this was the contents of COLUMN1</COLUMN1> 510<COLUMN2>this was the contents of COLUMN2</COLUMN2> 511 ]]> 512 </programlisting> 513 If you do not want your XML elements named after your database columns you have to work with array keys 514 (ELEMENT1 and ELEMENT2 in our example): 515 while the element specification 516 <programlisting role="php"> 517 <![CDATA[ 518array( 519 'ELEMENT1' => 'COLUMN1', 520 'ELEMENT2' => 'COLUMN2' 521); 522 ]]> 523 </programlisting> 524 This would make the same data appear like this: 525 <programlisting role="tutorial"> 526 <![CDATA[ 527<ELEMENT1>this was the contents of COLUMN1</ELEMENT1> 528<ELEMENT2>this was the contents of COLUMN2</ELEMENT2> 529 ]]> 530 </programlisting> 531 </para> 532 <para> 533 If you use both, the array key and the array value to specify an XML element, the array value can be of the following types: 534 <itemizedlist> 535 <listitem> 536 <emphasis>COLUMN NAME (string)</emphasis>: this is the default if not preceeded by ':' or '#'. 537 If the column does not exist, an {@link XML_Query2XML_ConfigException} will be thrown. 538 </listitem> 539 <listitem> 540 <emphasis>STATIC TEXT with a : prefix (string)</emphasis>: if the value is preceeded by a colon (':'), it is 541 interpreted as static text. 542 </listitem> 543 <listitem> 544 <emphasis>CALLBACK FUNCTION with a # prefix (string)</emphasis>: if the value is preceeded by a pound sign ('#'), it 545 is interpreted as a callback function. You can use a regular function (e.g. '#myFunction()') or a static 546 method (e.g. '#MyClass::myFunction()') - for how to use a non-static method, see the type COMMAND OBJECT. 547 The current record will be passed to the callback function 548 as an associative array. You can also pass additional string arguments to the callback function by specifing 549 them within the opening and closing brace; e.g. '#Utils::limit(12)' will result in Util::limit() being 550 called with the current record as the first and '12' as the second argument. If you do not want 551 to pass additional arguments to the callback function, the opening and closing brace are optional. 552 The callback function's return value will be converted to a string and used as the child text node 553 if it is anything but an object or an array. If you do return an object or an array from a callback 554 function it has to be an instance of {@link http://www.php.net/manual/en/ref.dom.php DOMNode} or 555 an array of DOMNode instances. Please see {@tutorial XML_Query2XML.pkg#other_xml_data_sources} for examples 556 and further details. If an instances of any other class is returned, a XML_Query2XML_XMLException will be thrown. 557 </listitem> 558 <listitem> 559 <emphasis>COMMAND OBJECT (object)</emphasis>: If you want to use a non-static method as a 560 callback function, you can do so by specifying the value as an instance of a class that 561 implements the {@link XML_Query2XML_Callback} interface. This implementation of the 562 {@link http://en.wikipedia.org/wiki/Command_pattern command pattern} gives you all the 563 flexibility. The disadvantage ist that you cannot use the XML UNSERIALIZATION prefix 564 or the CONDITIONAL prefix. Note: you have to require_once 'XML/Query2XML/Callback.php' 565 before using the XML_Query2XML_Callback interface. The return value of a COMMAND OBJECT's 566 execute() method is treated exactly the same as the return value of a CALLBACK FUNCTION. 567 </listitem> 568 </itemizedlist> 569 There are four more prefixes available that can be used in conjunction with all the prifixes described above: 570 <itemizedlist> 571 <listitem> 572 <emphasis>XML UNSERIALIZATION prefix &</emphasis>: the ampersand (&) prefix allows you to 573 automatically unserialize string data, i.e. transform a string into a {@link http://www.php.net/manual/en/ref.dom.php DOMDocument}. DOMDocument's 574 {@link http://www.php.net/manual/en/function.dom-domdocument-loadxml.php loadXML()} method will be 575 used for this purpose. You can combine all three types with this prefix: '&COLUMN_NAME', 576 '&#function()' or '&:<name>John</name>' will all work. You can even use the CONDITIONAL prefix 577 which has to preceed all other prefixes. If the data cannot be unserialized i.e. 578 {@link http://www.php.net/manual/en/function.dom-domdocument-loadxml.php DOMDocument::loadXML()} 579 returns false, a XML_Query2XML_XMLException will be thrown. Please see 580 {@tutorial XML_Query2XML.pkg#other_xml_data_sources} for examples and further 581 details. 582 </listitem> 583 <listitem> 584 <emphasis>BASE64 ENCODING prefix ^</emphasis>: if the specification starts with a carrat sign ('^'), 585 the element value will be passed to {@link http://www.php.net/base64_encode base64_encode()}. 586 The BASE64 ENCODING prefix can be used with all the prefixes described above (just put the BASE64 ENCODING prefix first): 587 e.g. '^#', '^:' or '^COLUMN_NAME'. 588 </listitem> 589 <listitem> 590 <emphasis>CDATA SECTION prefix =</emphasis>: if the specification starts with an equal sign ('='), 591 the element value will be enclosed in a CDATA section. A CDATA section starts with 592 "<![CDATA[" and ends with "]]>". 593 The CDATA SECTION prefix can be used with all the prefixes described above (just put the CDATA SECTION prefix first): 594 e.g. '=#', '=:', '=COLUMN_NAME' or '=^'. 595 </listitem> 596 <listitem> 597 <emphasis>CONDITIONAL prefix ?</emphasis>: if the specification starts with a question mark ('?'), 598 the whole element will be skipped if the value equals (==) an empty string. The CONDITIONAL prefix 599 can be combined with all types described above: if you do this you have to write the CONDITIONAL 600 prefix first e.g. '?#', '?:', '?&', '?=', '?^', or '?COLUMN_NAME'. 601 </listitem> 602 </itemizedlist> 603 Note: for ovious reasons, the prefix cannot be combined with a COMMAND OBJECT. 604 <para> 605 </para> 606 Basically, the same syntax can be use for 607 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_value}, 608 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes}, 609 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql.complexqueryspecification} and 610 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_idcolumn} because the private method 611 {@link XML_Query2XML::_applyColumnStringToRecord()} is used in all cases. 612 </para> 613 <para> 614 Let's start out with a very simple example. It will use 615 the column name as the XML element name for the first two columns but the 616 custom element name 'music_genre' for the column 'genre': 617 <programlisting role="php"> 618 <![CDATA[ 619<?php 620require_once 'XML/Query2XML.php'; 621require_once 'MDB2.php'; 622$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 623$dom = $query2xml->getXML( 624 "SELECT * FROM artist", 625 array( 626 'rootTag' => 'favorite_artists', 627 'idColumn' => 'artistid', 628 'rowTag' => 'artist', 629 'elements' => array( 630 'name', 631 'birth_year', 632 'music_genre' => 'genre' 633 ) 634 ) 635); 636header('Content-Type: application/xml'); 637$dom->formatOutput = true; 638print $dom->saveXML(); 639?> 640 ]]> 641 </programlisting> 642 This results in the following XML data: 643 <programlisting role="tutorial"> 644 <![CDATA[ 645<?xml version="1.0" encoding="UTF-8"?> 646<favorite_artists> 647 <artist> 648 <name>Curtis Mayfield</name> 649 <birth_year>1920</birth_year> 650 <music_genre>Soul</music_genre> 651 </artist> 652 <artist> 653 <name>Isaac Hayes</name> 654 <birth_year>1942</birth_year> 655 <music_genre>Soul</music_genre> 656 </artist> 657 <artist> 658 <name>Ray Charles</name> 659 <birth_year>1930</birth_year> 660 <music_genre>Country and Soul</music_genre> 661 </artist> 662</favorite_artists> 663 ]]> 664 </programlisting> 665 </para> 666 667 <para> 668 The following example demonstrates the usage of all different types: 669 <programlisting role="php"> 670 <![CDATA[ 671<?php 672require_once 'XML/Query2XML.php'; 673require_once 'XML/Query2XML/Callback.php'; 674require_once 'MDB2.php'; 675 676class Utils 677{ 678 function trim($record, $columnName) 679 { 680 return trim($record[$columnName]); 681 } 682 683 function getPublishedYearCentury($record) 684 { 685 return floor($record['published_year']/100); 686 } 687} 688 689class ToLowerCallback implements XML_Query2XML_Callback 690{ 691 private $_columnName = ''; 692 693 public function __construct($columnName) 694 { 695 $this->_columnName = $columnName; 696 } 697 698 public function execute(array $record) 699 { 700 return strtolower($record[$this->_columnName]); 701 } 702} 703 704$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 705$dom = $query2xml->getXML( 706 "SELECT 707 * 708 FROM 709 sale, 710 store, 711 album 712 WHERE 713 sale.store_id = store.storeid 714 AND 715 sale.album_id = album.albumid 716 AND 717 sale.timestamp < '2005-06-01'", 718 array( 719 'rootTag' => 'sales', 720 'idColumn' => 'saleid', 721 'rowTag' => 'sale', 722 'elements' => array( 723 'saleid', 724 'sale_timestamp' => 'timestamp', 725 'static' => ':some static text', 726 'now' => ':' . time(), 727 'album_century' => '#Utils::getPublishedYearCentury()', 728 'album_title' => '?#Utils::trim(title)', 729 'album_comment' => new ToLowerCallback('comment'), 730 'storeid', 731 'store_building1' => '?&building_xmldata', 732 'store_building2' => '?=building_xmldata', 733 'store_building3' => '?^building_xmldata' 734 ) 735 ) 736); 737 738header('Content-Type: application/xml'); 739$dom->formatOutput = true; 740print $dom->saveXML(); 741?> 742 ]]> 743 </programlisting> 744 Let's go through all simple element specifications, one by one: 745 <itemizedlist> 746 <listitem> 747 <emphasis>'saleid'</emphasis>: this is as simple as it can get. The value of the column saleid will be used for an element named saleid. 748 </listitem> 749 <listitem> 750 <emphasis>'sale_timestamp' => 'timestamp'</emphasis>: here we want to place the value of the 751 column timestamp in an element named sale_timestamp; we therefore use sale_timestamp as the array key. 752 </listitem> 753 <listitem> 754 <emphasis>'static' => ':some static text'</emphasis>: the STATIC TEXT (note the ":" prefix) "some static text" will be placed inside an element named static. 755 </listitem> 756 <listitem> 757 <emphasis>'now' => ':' . time()</emphasis>: here the static text is computed at run time; however it will be the same for all "now" elements. 758 </listitem> 759 <listitem> 760 <emphasis>'album_century' => '#Utils::getPublishedYearCentury()'</emphasis>: here we use 761 a CALLBACK FUNCTION with a "#" prefix; the return value of Utils::getPublishedYearCentury() is used as the XML element value. 762 Note that the callback function will automatically be called with the current $record as the first argument. 763 </listitem> 764 <listitem> 765 <emphasis>'album_title' => '?#Utils::trim(title)'</emphasis>: we also use a CALLBACK FUNCTION with a "#" prefix, but 766 this time we pass an additional string argument to our callback function by specifing it within the opening and 767 closing brace. Also, we use the CONDITIONAL prefix ? which means that the album_title element will only appear in 768 the generated XML data if Utils::trim() returned a non-empty string (to be precise a string that != ""). 769 </listitem> 770 <listitem> 771 <emphasis>'album_comment' => new ToLowerCallback('comment')</emphasis>: here we use a COMMAND OBJECT implementing 772 the {@link XML_Query2XML_Callback} interface. This is the object oriented way to use callbacks! Note how we pass the 773 column name to the callback class constructor, so that it's execute() method will now what column to work on. 774 </listitem> 775 <listitem> 776 <emphasis>'storeid'</emphasis>: plain an simple again 777 </listitem> 778 <listitem> 779 <emphasis>'store_building1' => '?&building_xmldata'</emphasis>: here we use the XML UNSERIALIZATION prefix "&" 780 to transform the value of the building_xmldata column into a DOMDocument. Using the CONDITIONAL prefix ? means 781 that store_building1 will only appear if building_xmldata is non-empty (!= "" to be precise). 782 </listitem> 783 <listitem> 784 <emphasis>'store_building2' => '?=building_xmldata'</emphasis>: CDATA SECTION prefix "=" is another way 785 incorporate XML data; the contents of the column building_xmldata will be surrounded by 786 "<![CDATA[" and "]]>". Using the CONDITIONAL prefix ? means 787 that store_building2 will only appear if building_xmldata is non-empty (!= "" to be precise). 788 </listitem> 789 <listitem> 790 <emphasis>'store_building3' => '?^building_xmldata'</emphasis>: here we use the BASE64 ENCODING prefix "^" 791 to first base64-encode the contents of the building_xmldata column. We again use the CONDITIONAL prefix "?". 792 </listitem> 793 </itemizedlist> 794 The resulting XML data looks like this: 795 <programlisting role="tutorial"> 796 <![CDATA[ 797<?xml version="1.0" encoding="UTF-8"?> 798<sales> 799 <sale> 800 <saleid>1</saleid> 801 <sale_timestamp>2005-05-25 07:32:00</sale_timestamp> 802 <static>some static text</static> 803 <now>1187498966</now> 804 <album_century>19</album_century> 805 <album_title>New World Order</album_title> 806 <album_comment>the best ever!</album_comment> 807 <storeid>1</storeid> 808 <store_building1> 809 <building> 810 <floors>4</floors> 811 <elevators>2</elevators> 812 <square_meters>3200</square_meters> 813 </building> 814 </store_building1> 815 <store_building2>< ![CDATA[<building><floors>4</floors><elevators>2</elevators><square_meters>3200</square_meters></building>]] ></store_building2> 816 <store_building3>PGJ1aWxkaW5nPjxmbG9vcnM+NDwvZmxvb3JzPjxlbGV2YXRvcnM+MjwvZWxldmF0b3JzPjxzcXVhcmVfbWV0ZXJzPjMyMDA8L3NxdWFyZV9tZXRlcnM+PC9idWlsZGluZz4=</store_building3> 817 </sale> 818 <sale> 819 <saleid>11</saleid> 820 <sale_timestamp>2005-05-25 07:23:00</sale_timestamp> 821 <static>some static text</static> 822 <now>1187498966</now> 823 <album_century>19</album_century> 824 <album_title>Curtis</album_title> 825 <album_comment>that man's got somthin' to say</album_comment> 826 <storeid>2</storeid> 827 <store_building1> 828 <building> 829 <floors>2</floors> 830 <elevators>1</elevators> 831 <square_meters>400</square_meters> 832 </building> 833 </store_building1> 834 <store_building2>< ![CDATA[<building><floors>2</floors><elevators>1</elevators><square_meters>400</square_meters></building>]] ></store_building2> 835 <store_building3>PGJ1aWxkaW5nPjxmbG9vcnM+MjwvZmxvb3JzPjxlbGV2YXRvcnM+MTwvZWxldmF0b3JzPjxzcXVhcmVfbWV0ZXJzPjQwMDwvc3F1YXJlX21ldGVycz48L2J1aWxkaW5nPg==</store_building3> 836 </sale> 837</sales> 838 ]]> 839 </programlisting> 840 Note: due to a bug in phpDocumentor I had to cheat a little bit in the above XML; as you might have noticed 841 there was a space between "<" and "![CDATA[". 842 </para> 843 </refsect3> 844 <refsect3 id="{@id complex_element_specifications}"> 845 <title>Complex Element Specifications</title> 846 <para> 847 A complex element specification consists of an array that can have all options 848 that can be present on the root level plus {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql} 849 and {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options}. 850 This allows for complete (and theoretically infinite) nesting. You will need to use it if the 851 child element should have attributes or child elements. 852 </para> 853 <para> 854 The following example is like the first one in 855 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications} 856 with one difference: the XML element 'name' should have the attribute 'type' set to the 857 static value 'full_name'. As attributes are not supported by simple elements specifications, 858 we have to use a complex element specification for the element 'name': 859 <programlisting role="php"> 860 <![CDATA[ 861<?php 862require_once 'XML/Query2XML.php'; 863require_once 'MDB2.php'; 864$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 865$dom = $query2xml->getXML( 866 "SELECT * FROM artist", 867 array( 868 'rootTag' => 'favorite_artists', 869 'idColumn' => 'artistid', 870 'rowTag' => 'artist', 871 'elements' => array( 872 'name' => array( 873 'value' => 'name', 874 'attributes' => array( 875 'type' => ':full_name' 876 ) 877 ), 878 'birth_year', 879 'music_genre' => 'genre' 880 ) 881 ) 882); 883header('Content-Type: application/xml'); 884$dom->formatOutput = true; 885print $dom->saveXML(); 886?> 887 ]]> 888 </programlisting> 889 This results in the following XML data: 890 <programlisting role="tutorial"> 891 <![CDATA[ 892<?xml version="1.0" encoding="UTF-8"?> 893<favorite_artists> 894 <artist> 895 <name type="full_name">Curtis Mayfield</name> 896 <birth_year>1920</birth_year> 897 <music_genre>Soul</music_genre> 898 </artist> 899 <artist> 900 <name type="full_name">Isaac Hayes</name> 901 <birth_year>1942</birth_year> 902 <music_genre>Soul</music_genre> 903 </artist> 904 <artist> 905 <name type="full_name">Ray Charles</name> 906 <birth_year>1930</birth_year> 907 <music_genre>Country and Soul</music_genre> 908 </artist> 909</favorite_artists> 910 ]]> 911 </programlisting> 912 </para> 913 <para> 914 Here is another little example: 915 <programlisting role="php"> 916 <![CDATA[ 917<?php 918require_once 'XML/Query2XML.php'; 919require_once 'MDB2.php'; 920$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 921$dom = $query2xml->getXML( 922 "SELECT * FROM artist LEFT JOIN album ON album.artist_id = artist.artistid", 923 array( 924 'rootTag' => 'music_library', 925 'rowTag' => 'artist', 926 'idColumn' => 'artistid', 927 'elements' => array( 928 'artistid', 929 'name', 930 'birth_year', 931 'birth_place', 932 'genre', 933 'albums' => array( 934 'rootTag' => 'albums', 935 'rowTag' => 'album', 936 'idColumn' => 'albumid', 937 'elements' => array('albumid', 'title', 'published_year') 938 ) 939 ) 940 ) 941); 942header('Content-Type: application/xml'); 943$dom->formatOutput = true; 944print $dom->saveXML(); 945?> 946 ]]> 947 </programlisting> 948 This results in the following XML data: 949 <programlisting role="tutorial"> 950 <![CDATA[ 951<?xml version="1.0" encoding="UTF-8"?> 952<music_library> 953 <artist> 954 <artistid>1</artistid> 955 <name>Curtis Mayfield</name> 956 <birth_year>1920</birth_year> 957 <birth_place>Chicago</birth_place> 958 <genre>Soul</genre> 959 <albums> 960 <album> 961 <albumid>1</albumid> 962 <title>New World Order</title> 963 <published_year>1990</published_year> 964 </album> 965 <album> 966 <albumid>2</albumid> 967 <title>Curtis</title> 968 <published_year>1970</published_year> 969 </album> 970 </albums> 971 </artist> 972 <artist> 973 <artistid>2</artistid> 974 <name>Isaac Hayes</name> 975 <birth_year>1942</birth_year> 976 <birth_place>Tennessee</birth_place> 977 <genre>Soul</genre> 978 <albums> 979 <album> 980 <albumid>3</albumid> 981 <title>Shaft</title> 982 <published_year>1972</published_year> 983 </album> 984 </albums> 985 </artist> 986 <artist> 987 <artistid>3</artistid> 988 <name>Ray Charles</name> 989 <birth_year>1930</birth_year> 990 <birth_place>Mississippi</birth_place> 991 <genre>Country and Soul</genre> 992 <albums /> 993 </artist> 994</music_library> 995 ]]> 996 </programlisting> 997 </para> 998 <para> 999 As we want for every artist only a single tag we need to identify each artist by the primary 1000 key of the table artist. Note that there is a second record for Curtis Mayfield (related to 1001 the album Curtis), but we don't want something like 1002 <programlisting role="tutorial"> 1003 <![CDATA[ 1004<artist> 1005 <name>Curtis Mayfield</name> 1006 <album> 1007 <name>New World Order</name> 1008 </album> 1009</artist> 1010<artist> 1011 <name>Curtis Mayfield</name> 1012 <album> 1013 <name>Curits</name> 1014 </album> 1015</artist> 1016 ]]> 1017 </programlisting> 1018 but rather 1019 <programlisting role="tutorial"> 1020 <![CDATA[ 1021<artist> 1022 <name>Curtis Mayfield</name> 1023 <albums> 1024 <album> 1025 <name>New World Order</name> 1026 </album> 1027 <albums> 1028 <name>Curtis</name> 1029 </albums> 1030 </albums> 1031</artist> 1032 ]]> 1033 </programlisting> 1034 This is achieved by telling XML_Query2XML which entity to focus on (on this level): the artist, as it 1035 is identified by the artist table's primary key. Once XML_Query2XML get's to the second Curtis Mayfield 1036 record, it can tell by the artistid 1 that an XML element was already created for this artist. 1037 </para> 1038 <para> 1039 For a one more example and a detailed explanation of complex child elements that have 1040 child elements themselves, see {@tutorial XML_Query2XML.pkg#casestudies.case02}. 1041 For an advanced example, see {@tutorial XML_Query2XML.pkg#casestudies.case05}. 1042 </para> 1043 </refsect3> 1044 <refsect3 id="{@id asterisk_shortcut}"> 1045 <title>Using the Asterisk Shortcut</title> 1046 <para> 1047 The asterisk shortcut only works with {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications} 1048 (and {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes.simple_attribute_specifications}). 1049 </para> 1050 <para> 1051 In some scenarios you will just want to use all columns found in the result set 1052 for {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications}. 1053 This is where the asterisk shortcut can come in very handy. An element specification that 1054 contains an asterisk (an "asterisk element specification") will be duplicated for each 1055 column present in the result set ($record). The simplest way of using the asterisk shortcut is this: 1056 <programlisting role="php"> 1057 <![CDATA[ 1058<?php 1059require_once 'XML/Query2XML.php'; 1060require_once 'MDB2.php'; 1061$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 1062$dom = $query2xml->getXML( 1063 "SELECT * FROM artist", 1064 array( 1065 'rootTag' => 'favorite_artists', 1066 'idColumn' => 'artistid', 1067 'rowTag' => 'artist', 1068 'elements' => array( 1069 '*' 1070 ) 1071 ) 1072); 1073header('Content-Type: application/xml'); 1074$dom->formatOutput = true; 1075print $dom->saveXML(); 1076?> 1077 ]]> 1078 </programlisting> 1079 As the result set contains the column artistid, name, birth_year, birth_place and genre the XML data will look like this: 1080 <programlisting role="tutorial"> 1081 <![CDATA[ 1082<?xml version="1.0" encoding="UTF-8"?> 1083<favorite_artists> 1084 <artist> 1085 <artistid>1</artistid> 1086 <name>Curtis Mayfield</name> 1087 <birth_year>1920</birth_year> 1088 <birth_place>Chicago</birth_place> 1089 <genre>Soul</genre> 1090 </artist> 1091 <artist> 1092 <artistid>2</artistid> 1093 <name>Isaac Hayes</name> 1094 <birth_year>1942</birth_year> 1095 <birth_place>Tennessee</birth_place> 1096 <genre>Soul</genre> 1097 </artist> 1098 <artist> 1099 <artistid>3</artistid> 1100 <name>Ray Charles</name> 1101 <birth_year>1930</birth_year> 1102 <birth_place>Mississippi</birth_place> 1103 <genre>Country and Soul</genre> 1104 </artist> 1105</favorite_artists> 1106 ]]> 1107 </programlisting> 1108 This is because internally, the array 1109 <programlisting role="php"> 1110 <![CDATA[ 1111'elements' => array( 1112 '*' 1113) 1114 ]]> 1115 </programlisting> 1116 is expanded to 1117 <programlisting role="php"> 1118 <![CDATA[ 1119'elements' => array( 1120 'artistid', 1121 'name', 1122 'birth_year', 1123 'birth_place', 1124 'genre' 1125) 1126 ]]> 1127 </programlisting> 1128 </para> 1129 <para> 1130 Think of the asterisk as a variable that will get replaced with each column name found in the result set: 1131 <programlisting role="php"> 1132 <![CDATA[ 1133<?php 1134require_once 'XML/Query2XML.php'; 1135require_once 'MDB2.php'; 1136$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 1137$dom = $query2xml->getXML( 1138 "SELECT * FROM artist", 1139 array( 1140 'rootTag' => 'favorite_artists', 1141 'idColumn' => 'artistid', 1142 'rowTag' => 'artist', 1143 'elements' => array( 1144 'TAG_*' => '#padWithHyphens(*)' 1145 ) 1146 ) 1147); 1148header('Content-Type: application/xml'); 1149$dom->formatOutput = true; 1150print $dom->saveXML(); 1151 1152function padWithHyphens($record, $columnName) 1153{ 1154 return '--' . $record[$columnName] . '--'; 1155} 1156?> 1157 ]]> 1158 </programlisting> 1159 The above code would result in the following data: 1160 <programlisting role="tutorial"> 1161 <![CDATA[ 1162<?xml version="1.0" encoding="UTF-8"?> 1163<favorite_artists> 1164 <artist> 1165 <TAG_artistid>--1--</TAG_artistid> 1166 <TAG_name>--Curtis Mayfield--</TAG_name> 1167 <TAG_birth_year>--1920--</TAG_birth_year> 1168 <TAG_birth_place>--Chicago--</TAG_birth_place> 1169 <TAG_genre>--Soul--</TAG_genre> 1170 </artist> 1171 <artist> 1172 <TAG_artistid>--2--</TAG_artistid> 1173 <TAG_name>--Isaac Hayes--</TAG_name> 1174 <TAG_birth_year>--1942--</TAG_birth_year> 1175 <TAG_birth_place>--Tennessee--</TAG_birth_place> 1176 <TAG_genre>--Soul--</TAG_genre> 1177 </artist> 1178 <artist> 1179 <TAG_artistid>--3--</TAG_artistid> 1180 <TAG_name>--Ray Charles--</TAG_name> 1181 <TAG_birth_year>--1930--</TAG_birth_year> 1182 <TAG_birth_place>--Mississippi--</TAG_birth_place> 1183 <TAG_genre>--Country and Soul--</TAG_genre> 1184 </artist> 1185</favorite_artists> 1186 ]]> 1187 </programlisting> 1188 </para> 1189 <para> 1190 You can also combine a simple element specification containing an asterisk shortcut with other (simple and complex) element specifications. 1191 The additional element specifications will be treated as an exception to the general rule set up by the asterisk element specification. 1192 The following code will produce a tag for each column in the result set containing the column's value. The only exeption is the column 1193 "genre" which we want to be different: the value should be all uppercase: 1194 <programlisting role="php"> 1195 <![CDATA[ 1196<?php 1197require_once 'XML/Query2XML.php'; 1198require_once 'MDB2.php'; 1199$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 1200$dom = $query2xml->getXML( 1201 "SELECT * FROM artist", 1202 array( 1203 'rootTag' => 'favorite_artists', 1204 'idColumn' => 'artistid', 1205 'rowTag' => 'artist', 1206 'elements' => array( 1207 '*' => '*', 1208 'genre' => '#genre2uppercase()' 1209 ) 1210 ) 1211); 1212header('Content-Type: application/xml'); 1213$dom->formatOutput = true; 1214print $dom->saveXML(); 1215 1216function genre2uppercase($record) 1217{ 1218 return strtoupper($record['genre']); 1219} 1220?> 1221 ]]> 1222 </programlisting> 1223 The resulting XML data looks like this: 1224 <programlisting role="tutorial"> 1225 <![CDATA[ 1226<?xml version="1.0" encoding="UTF-8"?> 1227<favorite_artists> 1228 <artist> 1229 <artistid>1</artistid> 1230 <name>Curtis Mayfield</name> 1231 <birth_year>1920</birth_year> 1232 <birth_place>Chicago</birth_place> 1233 <genre>SOUL</genre> 1234 </artist> 1235 <artist> 1236 <artistid>2</artistid> 1237 <name>Isaac Hayes</name> 1238 <birth_year>1942</birth_year> 1239 <birth_place>Tennessee</birth_place> 1240 <genre>SOUL</genre> 1241 </artist> 1242 <artist> 1243 <artistid>3</artistid> 1244 <name>Ray Charles</name> 1245 <birth_year>1930</birth_year> 1246 <birth_place>Mississippi</birth_place> 1247 <genre>COUNTRY AND SOUL</genre> 1248 </artist> 1249</favorite_artists> 1250 ]]> 1251 </programlisting> 1252 This is because internally, the array 1253 <programlisting role="php"> 1254 <![CDATA[ 1255'elements' => array( 1256 '*' => '*', 1257 'genre' => '#genre2uppercase()' 1258) 1259 ]]> 1260 </programlisting> 1261 is expanded to 1262 <programlisting role="php"> 1263 <![CDATA[ 1264'elements' => array( 1265 'artistid', 1266 'name', 1267 'birth_year', 1268 'birth_place', 1269 'genre' => '#genre2uppercase()' 1270) 1271 ]]> 1272 </programlisting> 1273 Please keep in mind that this also applies when combining an asterisk element specification with a complex element specification. 1274 That's why the following code would produce exactly the same XML data: 1275 <programlisting role="php"> 1276 <![CDATA[ 1277<?php 1278require_once 'XML/Query2XML.php'; 1279require_once 'MDB2.php'; 1280$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 1281$dom = $query2xml->getXML( 1282 "SELECT * FROM artist", 1283 array( 1284 'rootTag' => 'favorite_artists', 1285 'idColumn' => 'artistid', 1286 'rowTag' => 'artist', 1287 'elements' => array( 1288 '*' => '*', 1289 'genre' => array( 1290 'value' => '#genre2uppercase()' 1291 ) 1292 ) 1293 ) 1294); 1295header('Content-Type: application/xml'); 1296$dom->formatOutput = true; 1297print $dom->saveXML(); 1298 1299function genre2uppercase($record) 1300{ 1301 return strtoupper($record['genre']); 1302} 1303?> 1304 ]]> 1305 </programlisting> 1306 </para> 1307 <para> 1308 If we wanted to include all columns in the XML output except "genre" we could use a little trick: 1309 <programlisting role="php"> 1310 <![CDATA[ 1311<?php 1312require_once 'XML/Query2XML.php'; 1313require_once 'MDB2.php'; 1314$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 1315$dom = $query2xml->getXML( 1316 "SELECT * FROM artist", 1317 array( 1318 'rootTag' => 'favorite_artists', 1319 'idColumn' => 'artistid', 1320 'rowTag' => 'artist', 1321 'elements' => array( 1322 '*' => '*', 1323 'genre' => '?:' 1324 ) 1325 ) 1326); 1327header('Content-Type: application/xml'); 1328$dom->formatOutput = true; 1329print $dom->saveXML(); 1330?> 1331 ]]> 1332 </programlisting> 1333 In the resulting XML data the column "genre" is missing because we used the CONDITIONAL prefix '?' in combination with a static empty text: 1334 <programlisting role="tutorial"> 1335 <![CDATA[ 1336<?xml version="1.0" encoding="UTF-8"?> 1337<favorite_artists> 1338 <artist> 1339 <artistid>1</artistid> 1340 <name>Curtis Mayfield</name> 1341 <birth_year>1920</birth_year> 1342 <birth_place>Chicago</birth_place> 1343 </artist> 1344 <artist> 1345 <artistid>2</artistid> 1346 <name>Isaac Hayes</name> 1347 <birth_year>1942</birth_year> 1348 <birth_place>Tennessee</birth_place> 1349 </artist> 1350 <artist> 1351 <artistid>3</artistid> 1352 <name>Ray Charles</name> 1353 <birth_year>1930</birth_year> 1354 <birth_place>Mississippi</birth_place> 1355 </artist> 1356</favorite_artists> 1357 ]]> 1358 </programlisting> 1359 The exact same result could of course also be achieved using the "condition" option: 1360 <programlisting role="php"> 1361 <![CDATA[ 1362<?php 1363require_once 'XML/Query2XML.php'; 1364require_once 'MDB2.php'; 1365$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 1366$dom = $query2xml->getXML( 1367 "SELECT * FROM artist", 1368 array( 1369 'rootTag' => 'favorite_artists', 1370 'idColumn' => 'artistid', 1371 'rowTag' => 'artist', 1372 'elements' => array( 1373 '*' => '*', 1374 'genre' => array( 1375 'condition' => '#returnFalse()' 1376 //this would also work: 'condition' => ':' 1377 ) 1378 ) 1379 ) 1380); 1381header('Content-Type: application/xml'); 1382$dom->formatOutput = true; 1383print $dom->saveXML(); 1384 1385function returnFalse() 1386{ 1387 return false; 1388} 1389?> 1390 ]]> 1391 </programlisting> 1392 </para> 1393 <para> 1394 Another example of how to use the asterisk shortcut can be found in 1395 {@tutorial XML_Query2XML.pkg#casestudies.case07}. 1396 </para> 1397 <para> 1398 One final note on the asterisk shortcut: if you explicitly specify a tag name (an array element key) it has to contain an asterisk. The following code 1399 would cause a {@link XML_Query2XML_ConfigException} to be thrown: 1400 <programlisting role="php"> 1401 <![CDATA[ 1402<?php 1403require_once 'XML/Query2XML.php'; 1404require_once 'MDB2.php'; 1405$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 1406$dom = $query2xml->getXML( 1407 "SELECT * FROM artist", 1408 array( 1409 'rootTag' => 'favorite_artists', 1410 'idColumn' => 'artistid', 1411 'rowTag' => 'artist', 1412 'elements' => array( 1413 'tag' => '*' 1414 ) 1415 ) 1416); 1417header('Content-Type: application/xml'); 1418$dom->formatOutput = true; 1419print $dom->saveXML(); 1420?> 1421 ]]> 1422 </programlisting> 1423 This is because expanding 1424 <programlisting role="php"> 1425 <![CDATA[ 1426'elements' => array( 1427 'tag' => '*' 1428) 1429 ]]> 1430 </programlisting> 1431 to 1432 <programlisting role="php"> 1433 <![CDATA[ 1434'elements' => array( 1435 'tag' => 'artistid', 1436 'tag' => 'name', 1437 'tag' => 'birth_year', 1438 'tag' => 'birth_place', 1439 'tag' => 'genre' 1440) 1441 ]]> 1442 </programlisting> 1443 just makes no sense and therfore "*" is treated as a regular column name - which does not exist in this case! 1444 The exception's message would read: [elements]: The column "*" used in the option "tag" does not exist in the result set. 1445 </para> 1446 </refsect3> 1447 </refsect2> 1448 1449 <refsect2 id="{@id options_idcolumn}"> 1450 <title>$options['idColumn']</title> 1451 <para> 1452 In most cases this will be the name of the column by which a record is identified as unique, aka the 1453 primary key. This is especially important within a 1454 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.complex_element_specifications Complex Element Specification}. 1455 See there for an example. This option is obligatory at the root level! The idColumn specification can be 1456 of the following types: 1457 <itemizedlist> 1458 <listitem> 1459 <emphasis>COLUMN NAME</emphasis>: this is the default if not preceeded by ':' or '#'. 1460 If the column does not exist, an {@link XML_Query2XML_ConfigException} will be thrown. 1461 The current record (not the one of the parent level) will be used. 1462 </listitem> 1463 <listitem> 1464 <emphasis>STATIC TEXT with a : prefix</emphasis>: if the value is preceeded by a colon (':'), it is 1465 interpreted as static text. 1466 </listitem> 1467 <listitem> 1468 <emphasis>CALLBACK FUNCTION with a # prefix</emphasis>: if the value is preceeded by a pound sign ('#'), it 1469 is interpreted as a callback function. You can use a regular function (e.g. '#myFunction()') or a static 1470 method (e.g. '#MyClass::myFunction()') - for how to use a non-static method, see the type COMMAND OBJECT. 1471 The current record will be passed to the callback function 1472 as an associative array. You can also pass additional string arguments to the callback function by specifing 1473 them within the opening and closing brace; e.g. '#Utils::limit(12)' will result in Util::limit() being 1474 called with the current record as the first and '12' as the second argument. If you do not want 1475 to pass additional arguments to the callback function, the opening and closing brace are optional. 1476 </listitem> 1477 <listitem> 1478 <emphasis>COMMAND OBJECT (object)</emphasis>: If you want to use a non-static method as a 1479 callback function, you can do so by specifying the value as an instance of a class that 1480 implements the {@link XML_Query2XML_Callback} interface. This implementation of the 1481 {@link http://en.wikipedia.org/wiki/Command_pattern command pattern} gives you all the 1482 flexibility. Note: you have to require_once 'XML/Query2XML/Callback.php' 1483 before using the XML_Query2XML_Callback interface. The return value of a COMMAND OBJECT's 1484 execute() method is treated exactly the same as the return value of a CALLBACK FUNCTION. 1485 </listitem> 1486 <listitem> 1487 <emphasis>FALSE (boolean)</emphasis>: Only use this if you don't have a primary key 1488 (which is a very bad idea) or you have a very simple tasks at hand like retrieving all 1489 records from a table. Using the value FALSE will make XML_Query2XML treat every record 1490 as unique. WARNING: it is considered very bad practice to use a value of 1491 FALSE if you have a way to specify your primar key. This is because your code might 1492 change over time and having your primary key specified will just make your more stable. 1493 For a legitimate use of the value FALSE for the option idColumn, please see 1494 {@tutorial XML_Query2XML.pkg#dumpdatabase}. 1495 </listitem> 1496 </itemizedlist> 1497 The same syntax (with the additional '?' prefix but without the boolean value FALSE) can 1498 be use for 1499 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_value}, 1500 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes.simple_attribute_specifications}, 1501 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql.complexqueryspecification} and 1502 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications} because the private method 1503 {@link XML_Query2XML::_applyColumnStringToRecord()} is used in all cases. 1504 </para> 1505 <para> 1506 For example and further discussion of {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_idcolumn} 1507 please see {@tutorial XML_Query2XML.pkg#casestudies.case02}. 1508 </para> 1509 <refsect3 id="{@id multicolumn_pk}"> 1510 <title>Handling Multi-Column Primary Keys</title> 1511 <para> 1512 Sometimes your primary key will consist of multiple columns. For example, this might 1513 be the case when you implement a many-to-many relationship using an intersection table. 1514 </para> 1515 <para> 1516 But as you know by now, 1517 $options['idColumn'] has to evaluate to one unique ID for every record. 1518 Depending on the type of the primary key columns you will want to choose a different strategy to 1519 compute that unique ID for every record. To begin with, you have to choose whether 1520 you want to compute that unique ID within the database or using PHP. To do it within the 1521 database you will have to define an alias using the "... AS alias_name" syntax. Using 1522 PHP you have to use a callback function to generate the ID. When generating the ID, you 1523 again have different options. 1524 </para> 1525 <para> 1526 If your primary key columns are of a numeric type, you can 1527 <itemizedlist> 1528 <listitem> 1529 Concatenate your primary key columns using a separator. In the database 1530 you would use something like "CONCAT(column1, '_', column2) AS id" and 1531 when implemented in PHP you would write a callback that returns something 1532 like "$record['column1'] . '_' . $record['column2']". 1533 </listitem> 1534 <listitem> 1535 If Leftshift the first column by the number of bits the second column consumes 1536 and OR the leftshifted first column with the second column. E.g. if you had 1537 two columns defined as TINYINT UNSIGNED (0 to 255, i.e. 8 bits), you could 1538 generate the ID by 1539 <programlisting role="php"> 1540 <![CDATA[ 1541$id = ($column1 << 8) | $column2; 1542 ]]> 1543 </programlisting> 1544 Think of like this: 1545 <![CDATA[ 1546$column1 (set to 255): 00000000000000000000000011111111 1547$column2 left-shifted by 8 bits: 00000000000000001111111100000000 1548 1549now we OR the left-shifted $column1 with $column2 (both were 1550originally set to the maximum of 255): 1551$column1 (left-shifted 255): 00000000000000001111111100000000 1552$column2 (set to 255): 00000000000000000000000011111111 1553------------------------------------------------------------- 1554result: 00000000000000001111111111111111 1555As you can see two different combinations of $column1 and $column2 1556will always result in a different ID. This is because the left-shifted 1557$column1 does not intersect with $column2. 1558 ]]> 1559 <emphasis>WARNING: an integer in PHP has 32 bits</emphasis> - 1560 the tecnique described above therefore only works if the sum 1561 of the bits consumed by your primary key columns is less than 1562 or equal to 32 (i.e. two 16 bit numbers, four 8 bit numbers or 1563 two 8 bit numbers and one 16 bit number). 1564 </listitem> 1565 <listitem> 1566 Use the generateIdFromMultiKeyPK() method described below. 1567 </listitem> 1568 </itemizedlist> 1569 </para> 1570 <para> 1571 If your primary key columns are of a character type (e.g. CHAR, VARCHAR) you 1572 have to come up with something else. Before you read on, I strongly urge 1573 you to reconsider your choice for the primary key (does it really meet the 1574 requirements of minimality and stability, i.e. is immutable?). 1575 <emphasis>SECURITY WARNING:</emphasis> 1576 Do not simply concatenate your character type columns (with or without a separator). 1577 The following example shows why: 1578 <![CDATA[ 1579record1: column1='a_b' column2='c' 1580record2: column1='a' column2='b_c' 1581When using the separator '_' both records would have an ID of 'a_b_c'. 1582 ]]> 1583 A malicious attacker could use your separator within 1584 one of the column values to force ID collisions, which potentially 1585 lead to an exploitable security vulnerability. Great care should therefore 1586 be taken when choosing a separator - and relying on its confidentiality is 1587 not a good strategy. What you might do is to use a separator that is longer 1588 than the maximum character length of your primary key columns. But this only 1589 makes sense if that maximum is rather low. For example, if you have two CHAR(2) 1590 columns, it is reasonable to use the separator '---' which is three characters long. 1591 </para> 1592 <para> 1593 Another thing one might think of is to use a hash function like sha1() or md5(). 1594 But that's not really an option as it would really kill the performance of your 1595 application. 1596 </para> 1597 <para> 1598 The most bullet proof solution to the problem of generating a unique ID from 1599 two character type columns is to use a callback function that works with 1600 an array. The following function can be used as a callback whenever 1601 you need to generate an ID from two character type columns. 1602 <programlisting role="php"> 1603 <![CDATA[ 1604<?php 1605/**Returns a unique ID base on the values stored in 1606* $record[$columnName1] and $record[$columnName2]. 1607* 1608* @param array $record An associative array. 1609* @param string $columnName1 The name of the first column. 1610* @param string $columnName2 The name of the second column. 1611* @return int The ID. 1612*/ 1613function generateIdFromMultiKeyPK($record, $columnName1, $columnName2) 1614{ 1615 static $ids = array(); 1616 static $idCounter = 0; 1617 1618 $column1 = $record[$columnName1]; 1619 $column2 = $record[$columnName2]; 1620 if (!isset($ids[$column1])) { 1621 $ids[$column1] = array(); 1622 } 1623 if (!isset($ids[$column1][$column2])) { 1624 $ids[$column1][$column2] = $idCounter++; 1625 } 1626 return $ids[$column1][$column2]; 1627} 1628?> 1629 ]]> 1630 </programlisting> 1631 </para> 1632 <para> 1633 All you have to do is to specify $options['idColumn'] as: 1634 <![CDATA[ 1635'#generateIdFromMultiKeyPK(name_of_column1, name_of_column2)' 1636 ]]> 1637 Remember: $record is automatically passed as the first argument 1638 to the callback function. 1639 </para> 1640 </refsect3> 1641 </refsect2> 1642 1643 <refsect2 id="{@id options_attributes}"> 1644 <title>$options['attributes']</title> 1645 <para> 1646 This option is an array that holds columns to include in the XML data as 1647 attributes. {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes.simple_attribute_specifications Simple} 1648 and {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes.complex_attribute_specifications complex} attribute 1649 specifications are supported. 1650 </para> 1651 <para> 1652 If you want to add attributes to the root element (i.e. the first child of the DOMDocument instance returned by {@link XML_Query2XML::getXML() getXML()}), 1653 please see {@tutorial XML_Query2XML.pkg#query2xml_getxml.return_value_modification}. 1654 </para> 1655 <refsect3 id="{@id simple_attribute_specifications}"> 1656 <title>Simple Attribute Specifications</title> 1657 <para> 1658 It works like 1659 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications}: 1660 the column names are the array values. By default the column's value 1661 will be put into an attribute named after the column. If you're 1662 unhappy with the default you can specify an other attribute name by using 1663 it as the array key. As documented for {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications} 1664 the prefixes "?", "#", "^" and ":" or a COMMAND OBJECT can be used. Only the UNSERIALIZATION prefix & and the CDATA SECTION prefix ^ which are valid for 1665 a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications Simple Element Specification} 1666 cannot be used for a Simple Attribute Specification. 1667 </para> 1668 <para> 1669 The follwing example will use 1670 the column name as the attribute name for the first two columns but the 1671 custom attribute name 'music_genre' for the column 'genre': 1672 <programlisting role="php"> 1673 <![CDATA[ 1674<?php 1675require_once 'XML/Query2XML.php'; 1676require_once 'MDB2.php'; 1677$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 1678$dom = $query2xml->getXML( 1679 "SELECT * FROM artist", 1680 array( 1681 'rootTag' => 'favorite_artists', 1682 'idColumn' => 'artistid', 1683 'rowTag' => 'artist', 1684 'elements' => array(), 1685 'attributes' => array( 1686 'name', 1687 'birth_year', 1688 'music_genre' => 'genre' 1689 ) 1690 ) 1691); 1692header('Content-Type: application/xml'); 1693$dom->formatOutput = true; 1694print $dom->saveXML(); 1695?> 1696 ]]> 1697 </programlisting> 1698 This results in the following XML data: 1699 <programlisting role="tutorial"> 1700 <![CDATA[ 1701<?xml version="1.0" encoding="UTF-8"?> 1702<favorite_artists> 1703 <artist name="Curtis Mayfield" birth_year="1920" music_genre="Soul"/> 1704 <artist name="Isaac Hayes" birth_year="1942" music_genre="Soul"/> 1705 <artist name="Ray Charles" birth_year="1930" music_genre="Country and Soul"/> 1706</favorite_artists> 1707 ]]> 1708 </programlisting> 1709 </para> 1710 </refsect3> 1711 <refsect3 id="{@id complex_attribute_specifications}"> 1712 <title>Complex Attribute Specifications</title> 1713 <para> 1714 A complex attribute specification consists of an array that must contain 1715 <itemizedlist> 1716 <listitem> 1717 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_value}: the attribute's value 1718 (note: you cannot use the UNSERIALIZATION prefix & or the the CDATA SECTION prefix ^ for an attribute specification) 1719 </listitem> 1720 </itemizedlist> 1721 and optionally can contain 1722 <itemizedlist> 1723 <listitem> 1724 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_condition}: condition for the inclusion of the attribute 1725 </listitem> 1726 <listitem> 1727 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_encoder}: encoding of the attribute value 1728 </listitem> 1729 <listitem> 1730 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql}: allows you to fetch more data 1731 </listitem> 1732 <listitem> 1733 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options}: options for the sql option 1734 </listitem> 1735 </itemizedlist> 1736 The array key used to store the complex attribute specification is always used as the attribute's name. 1737 Unlike {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.complex_element_specifications} 1738 complex attribute specifications cannot be nested for obvious reasons. Complex attribute specifications 1739 should only be used for the following reasons: 1740 <itemizedlist> 1741 <listitem> 1742 the attribute is only to be included under a condition that cannot be expressed using the '?' prefix 1743 within a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes.simple_attribute_specifications simple attribute specification} 1744 </listitem> 1745 <listitem> 1746 additional data is needed from the database 1747 </listitem> 1748 </itemizedlist> 1749 In all other cases {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes.simple_attribute_specifications} 1750 should be used as they will make your code run faster. 1751 </para> 1752 <para> 1753 To add a "bornBefore1940" attribute only to those artists that were born before 1940 we could write: 1754 <programlisting role="php"> 1755 <![CDATA[ 1756<?php 1757require_once 'XML/Query2XML.php'; 1758require_once 'MDB2.php'; 1759$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 1760$dom = $query2xml->getXML( 1761 "SELECT * FROM artist", 1762 array( 1763 'rootTag' => 'favorite_artists', 1764 'idColumn' => 'artistid', 1765 'rowTag' => 'artist', 1766 'attributes' => array( 1767 'name', 1768 'birth_year', 1769 'bornBefore1940' => array( 1770 'value' => ':true', 1771 'condition' => '#lessThan(birth_year, 1940)' 1772 ) 1773 ) 1774 ) 1775); 1776header('Content-Type: application/xml'); 1777$dom->formatOutput = true; 1778print $dom->saveXML(); 1779 1780function lessThan($record, $columnName, $num) 1781{ 1782 return $record[$columnName] < $num; 1783} 1784?> 1785 ]]> 1786 </programlisting> 1787 This results in the following XML data: 1788 <programlisting role="tutorial"> 1789 <![CDATA[ 1790<?xml version="1.0" encoding="UTF-8"?> 1791<favorite_artists> 1792 <artist birth_year="1920" bornBefore1940="true" name="Curtis Mayfield" /> 1793 <artist birth_year="1942" name="Isaac Hayes" /> 1794 <artist birth_year="1930" bornBefore1940="true" name="Ray Charles" /> 1795</favorite_artists> 1796 ]]> 1797 </programlisting> 1798 </para> 1799 <para> 1800 In the next example we want a "firstAlbumTitle" attribute for each artist. 1801 For the purpose of the example we will not use a single left outer join but a complex attribute specification with the "sql" option. 1802 As retrieving more than one record for a single attribute makes no sense 1803 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options.single_record} is always automatically set to true when fetching records 1804 for attributes. 1805 <programlisting role="php"> 1806 <![CDATA[ 1807<?php 1808require_once 'XML/Query2XML.php'; 1809require_once 'MDB2.php'; 1810$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 1811$dom = $query2xml->getXML( 1812 "SELECT * FROM artist", 1813 array( 1814 'rootTag' => 'favorite_artists', 1815 'idColumn' => 'artistid', 1816 'rowTag' => 'artist', 1817 'attributes' => array( 1818 'name', 1819 'birth_year', 1820 'firstAlbumTitle' => array( 1821 'value' => 'title', 1822 'sql' => array( 1823 'data' => array( 1824 'artistid' 1825 ), 1826 'query' => "SELECT * FROM album WHERE artist_id = ? ORDER BY published_year" 1827 ) 1828 ) 1829 ) 1830 ) 1831); 1832header('Content-Type: application/xml'); 1833$dom->formatOutput = true; 1834print $dom->saveXML(); 1835?> 1836 ]]> 1837 </programlisting> 1838 This results in the following XML data: 1839 <programlisting role="tutorial"> 1840 <![CDATA[ 1841<?xml version="1.0" encoding="UTF-8"?> 1842<favorite_artists> 1843 <artist birth_year="1920" firstAlbumTitle="Curtis" name="Curtis Mayfield" /> 1844 <artist birth_year="1942" firstAlbumTitle="Shaft" name="Isaac Hayes" /> 1845 <artist birth_year="1930" name="Ray Charles" /> 1846</favorite_artists> 1847 ]]> 1848 </programlisting> 1849 As you can see, the firstAlbumTitle attribute is missing for Ray Charles. 1850 This is because he does not have any albums in our test database and processing 1851 the "value" option without any records just makes no sense. 1852 </para> 1853 <para> 1854 In the last example I'd like to demonstrate the use of {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options} within 1855 a complex attribute specification. As stated before, {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options.single_record} 1856 is always automatically set to true - no matter what you assign to it. 1857 This time, we want a "firstAlbum" attribute that has a value of "TITLE (GENRE)" - remember that 1858 "genre" is a colum of the artist table while "title" is a column of the album table. 1859 <programlisting role="php"> 1860 <![CDATA[ 1861<?php 1862require_once 'XML/Query2XML.php'; 1863require_once 'MDB2.php'; 1864$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 1865$dom = $query2xml->getXML( 1866 "SELECT * FROM artist", 1867 array( 1868 'rootTag' => 'favorite_artists', 1869 'idColumn' => 'artistid', 1870 'rowTag' => 'artist', 1871 'attributes' => array( 1872 'name', 1873 'birth_year', 1874 'firstAlbum' => array( 1875 'value' => '#combineTitleAndGenre()', 1876 'sql' => array( 1877 'data' => array( 1878 'artistid' 1879 ), 1880 'query' => "SELECT * FROM album WHERE artist_id = ? ORDER BY published_year" 1881 ), 1882 'sql_options' => array( 1883 'merge_selective' => array('genre') 1884 ) 1885 ) 1886 ) 1887 ) 1888); 1889header('Content-Type: application/xml'); 1890$dom->formatOutput = true; 1891print $dom->saveXML(); 1892 1893function combineTitleAndGenre($record) 1894{ 1895 return $record['title'] . ' (' . $record['genre'] . ')'; 1896} 1897?> 1898 ]]> 1899 </programlisting> 1900 This results in the following XML data: 1901 <programlisting role="tutorial"> 1902 <![CDATA[ 1903<?xml version="1.0" encoding="UTF-8"?> 1904<favorite_artists> 1905 <artist name="Curtis Mayfield" birth_year="1920" firstAlbum="Curtis (Soul)"/> 1906 <artist name="Isaac Hayes" birth_year="1942" firstAlbum="Shaft (Soul)"/> 1907 <artist name="Ray Charles" birth_year="1930"/> 1908</favorite_artists> 1909 ]]> 1910 </programlisting> 1911 </para> 1912 </refsect3> 1913 <refsect3 id="{@id asterisk_shortcut}"> 1914 <title>Using the Asterisk Shortcut</title> 1915 <para> 1916 The asterisk shortcut only works with {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes.simple_attribute_specifications} 1917 (and {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications}). 1918 </para> 1919 <para> 1920 Everything said about 1921 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.asterisk_shortcut Using the Asterisk Shortcut with simple element specifications} 1922 applies here to! 1923 The simplest example of using the asterisk shortcut with the attributes option is as follows: 1924 <programlisting role="php"> 1925 <![CDATA[ 1926<?php 1927require_once 'XML/Query2XML.php'; 1928require_once 'MDB2.php'; 1929$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 1930$dom = $query2xml->getXML( 1931 "SELECT * FROM artist", 1932 array( 1933 'rootTag' => 'favorite_artists', 1934 'idColumn' => 'artistid', 1935 'rowTag' => 'artist', 1936 'attributes' => array( 1937 '*' 1938 ) 1939 ) 1940); 1941header('Content-Type: application/xml'); 1942$dom->formatOutput = true; 1943print $dom->saveXML(); 1944?> 1945 ]]> 1946 </programlisting> 1947 This produces this XML data: 1948 <programlisting role="tutorial"> 1949 <![CDATA[ 1950<?xml version="1.0" encoding="UTF-8"?> 1951<favorite_artists> 1952 <artist artistid="1" birth_place="Chicago" birth_year="1920" genre="Soul" name="Curtis Mayfield" /> 1953 <artist artistid="2" birth_place="Tennessee" birth_year="1942" genre="Soul" name="Isaac Hayes" /> 1954 <artist artistid="3" birth_place="Mississippi" birth_year="1930" genre="Country and Soul" name="Ray Charles" /> 1955</favorite_artists> 1956 ]]> 1957 </programlisting> 1958 </para> 1959 </refsect3> 1960 </refsect2> 1961 1962 <refsect2 id="{@id options_rowtag}"> 1963 <title>$options['rowTag']</title> 1964 <para> 1965 The name of the tag that encloses each record. The default is 'row'. 1966 </para> 1967 <para> 1968 Here goes an example of 'rowTag' being used at the root level: 1969 <programlisting role="php"> 1970 <![CDATA[ 1971<?php 1972require_once 'XML/Query2XML.php'; 1973require_once 'MDB2.php'; 1974$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 1975$dom = $query2xml->getXML( 1976 "SELECT * FROM artist", 1977 array( 1978 'rootTag' => 'favorite_artists', 1979 'idColumn' => 'artistid', 1980 'rowTag' => 'artist', 1981 'elements' => array( 1982 'name', 1983 'birth_year', 1984 'genre' 1985 ) 1986 ) 1987); 1988header('Content-Type: application/xml'); 1989$dom->formatOutput = true; 1990print $dom->saveXML(); 1991?> 1992 ]]> 1993 </programlisting> 1994 'rowTag' was set to 'artist' therefore the resulting XML data is: 1995 <programlisting role="tutorial"> 1996 <![CDATA[ 1997<?xml version="1.0" encoding="UTF-8"?> 1998<favorite_artists> 1999 <artist> 2000 <name>Curtis Mayfield</name> 2001 <birth_year>1920</birth_year> 2002 <genre>Soul</genre> 2003 </artist> 2004 <artist> 2005 <name>Isaac Hayes</name> 2006 <birth_year>1942</birth_year> 2007 <genre>Soul</genre> 2008 </artist> 2009 <artist> 2010 <name>Ray Charles</name> 2011 <birth_year>1930</birth_year> 2012 <genre>Country and Soul</genre> 2013 </artist> 2014</favorite_artists> 2015 ]]> 2016 </programlisting> 2017 </para> 2018 <para> 2019 Now let's have a look at a more advanced example: 2020 <programlisting role="php"> 2021 <![CDATA[ 2022<?php 2023require_once 'XML/Query2XML.php'; 2024require_once 'MDB2.php'; 2025$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 2026$dom = $query2xml->getXML( 2027 "SELECT 2028 * 2029 FROM 2030 artist 2031 LEFT JOIN album ON album.artist_id = artist.artistid", 2032 array( 2033 'rootTag' => 'music_library', 2034 'rowTag' => 'artist', 2035 'idColumn' => 'artistid', 2036 'elements' => array( 2037 'artistid', 2038 'name', 2039 'birth_year', 2040 'birth_place', 2041 'genre', 2042 'albums' => array( 2043 'rootTag' => 'albums', 2044 'rowTag' => 'album', 2045 'idColumn' => 'albumid', 2046 'elements' => array( 2047 'albumid', 2048 'title', 2049 'published_year', 2050 'comment' 2051 ) 2052 ) 2053 ) 2054 ) 2055); 2056 2057header('Content-Type: application/xml'); 2058$dom->formatOutput = true; 2059print $dom->saveXML(); 2060?> 2061 ]]> 2062 </programlisting> 2063 Here 'rowTag' on the root level is set to 'artist' while ['elements']['albums']['rowTag'] 2064 is set to 'album'. This example is taken from {@tutorial XML_Query2XML.pkg#casestudies.case02}, 2065 so please see there for the resulting XML data and further discussion. 2066 </para> 2067 <para> 2068 In some situations, 'rowTag' can be omitted all together: 2069 <programlisting role="php"> 2070 <![CDATA[ 2071<?php 2072require_once 'XML/Query2XML.php'; 2073require_once 'MDB2.php'; 2074$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 2075$dom = $query2xml->getXML( 2076 "SELECT * FROM artist", 2077 array( 2078 'rootTag' => 'favorite_artists', 2079 'idColumn' => 'artistid', 2080 'rowTag' => 'artist', 2081 'elements' => array( 2082 'name' => array( 2083 'value' => 'name', 2084 'attributes' => array( 2085 'type' => ':full_name' 2086 ) 2087 ), 2088 'birth_year', 2089 'music_genre' => 'genre' 2090 ) 2091 ) 2092); 2093header('Content-Type: application/xml'); 2094$dom->formatOutput = true; 2095print $dom->saveXML(); 2096?> 2097 ]]> 2098 </programlisting> 2099 Here the complex element definition ['elements']['name'] has no 'rowTag' option. This is alright 2100 because the specification's array key ('name' in this case) is used by default. 2101 </para> 2102 </refsect2> 2103 2104 <refsect2 id="{@id options_dynamicrowtag}"> 2105 <title>$options['dynamicRowTag']</title> 2106 <para> 2107 Use this option if you want the name of an XML element determined at run time (e.g. you want to pull the 2108 XML element name from the database). Note: if this option is present, 2109 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_rowtag} will be ignored. 2110 </para> 2111 <para> 2112 What you can assign to $options['dynamicRowTag'] is very similar as what you can use for 2113 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_value} 2114 or a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications Simple Element Specification}. 2115 2116 $options['dynamicRowTag'] can be of the following types: 2117 <itemizedlist> 2118 <listitem> 2119 <emphasis>COLUMN NAME</emphasis>: this is the default if not preceeded by ':' or '#'. 2120 If the column does not exist, an {@link XML_Query2XML_ConfigException} will be thrown. 2121 </listitem> 2122 <listitem> 2123 <emphasis>STATIC TEXT with a : prefix</emphasis>: if the value is preceeded by a colon (':'), it is 2124 interpreted as static text. 2125 </listitem> 2126 <listitem> 2127 <emphasis>CALLBACK FUNCTION with a # prefix</emphasis>: if the value is preceeded by a pound sign ('#'), it 2128 is interpreted as a callback function. You can use a regular function (e.g. '#myFunction()') or a static 2129 method (e.g. '#MyClass::myFunction()') - for how to use a non-static method, see the type COMMAND OBJECT. 2130 The current record will be passed to the callback function 2131 as an associative array. You can also pass additional string arguments to the callback function by specifing 2132 them within the opening and closing brace; e.g. '#Utils::limit(12)' will result in Util::limit() being 2133 called with the current record as the first and '12' as the second argument. If you do not want 2134 to pass additional arguments to the callback function, the opening and closing brace are optional. 2135 The callback function's return value obviously has to be a string that is a valid XML element name. 2136 </listitem> 2137 <listitem> 2138 <emphasis>COMMAND OBJECT (object)</emphasis>: If you want to use a non-static method as a 2139 callback function, you can do so by specifying the value as an instance of a class that 2140 implements the {@link XML_Query2XML_Callback} interface. This implementation of the 2141 {@link http://en.wikipedia.org/wiki/Command_pattern command pattern} gives you all the 2142 flexibility. The disadvantage ist that you cannot use the XML UNSERIALIZATION prefix 2143 or the CONDITIONAL prefix. Note: you have to require_once 'XML/Query2XML/Callback.php' 2144 before using the XML_Query2XML_Callback interface. The return value of a COMMAND OBJECT's 2145 execute() method is treated exactly the same as the return value of a CALLBACK FUNCTION. 2146 </listitem> 2147 </itemizedlist> 2148 </para> 2149 <para> 2150 Let's have a look at a straightforward example: we want our customer's email addresses inside a tag named 2151 after the customer's first name, e.g. <John>john.doe@example.com</John>: 2152 <programlisting role="php"> 2153 <![CDATA[ 2154<?php 2155require_once 'XML/Query2XML.php'; 2156require_once 'MDB2.php'; 2157$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 2158$dom = $query2xml->getXML( 2159 "SELECT * FROM customer", 2160 array( 2161 'rootTag' => 'customers', 2162 'idColumn' => 'customerid', 2163 'rowTag' => 'customer', 2164 'elements' => array( 2165 'customerid', 2166 'name_and_email' => array( 2167 'dynamicRowTag' => 'first_name', 2168 'value' => 'email' 2169 ) 2170 ) 2171 ) 2172); 2173header('Content-Type: application/xml'); 2174$dom->formatOutput = true; 2175print $dom->saveXML(); 2176?> 2177 ]]> 2178 </programlisting> 2179 The resulting XML looks like this: 2180 <programlisting role="tutorial"> 2181 <![CDATA[ 2182<?xml version="1.0" encoding="UTF-8"?> 2183<customers> 2184 <customer> 2185 <customerid>1</customerid> 2186 <Jane>jane.doe@example.com</Jane> 2187 </customer> 2188 <customer> 2189 <customerid>2</customerid> 2190 <John>john.doe@example.com</John> 2191 </customer> 2192 <customer> 2193 <customerid>3</customerid> 2194 <Susan>susan.green@example.com</Susan> 2195 </customer> 2196 <customer> 2197 <customerid>4</customerid> 2198 <Victoria>victory.alt@example.com</Victoria> 2199 </customer> 2200 <customer> 2201 <customerid>5</customerid> 2202 <Will>will.wippy@example.com</Will> 2203 </customer> 2204 <customer> 2205 <customerid>6</customerid> 2206 <Tim>tim.raw@example.com</Tim> 2207 </customer> 2208 <customer> 2209 <customerid>7</customerid> 2210 <Nick>nick.fallow@example.com</Nick> 2211 </customer> 2212 <customer> 2213 <customerid>8</customerid> 2214 <Ed>ed.burton@example.com</Ed> 2215 </customer> 2216 <customer> 2217 <customerid>9</customerid> 2218 <Jack>jack.woo@example.com</Jack> 2219 </customer> 2220 <customer> 2221 <customerid>10</customerid> 2222 <Maria>maria.gonzales@example.com</Maria> 2223 </customer> 2224</customers> 2225 ]]> 2226 </programlisting> 2227 2228 </para> 2229 </refsect2> 2230 2231 <refsect2 id="{@id options_roottag}"> 2232 <title>$options['rootTag']</title> 2233 <para> 2234 The name of the root tag that encloses all other tags. On the root level, the default is 'root'. 2235 On all other levels omitting the rootTag option means that the row tags will not be enclosed by 2236 a root tag but will directly be placed inside the parent tag. 2237 </para> 2238 <para> 2239 Here goes an example of 'rootTag' being used at the root level: 2240 <programlisting role="php"> 2241 <![CDATA[ 2242<?php 2243require_once 'XML/Query2XML.php'; 2244require_once 'MDB2.php'; 2245$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 2246$dom = $query2xml->getXML( 2247 "SELECT * FROM artist", 2248 array( 2249 'rootTag' => 'favorite_artists', 2250 'idColumn' => 'artistid', 2251 'rowTag' => 'artist', 2252 'elements' => array( 2253 'name', 2254 'birth_year', 2255 'genre' 2256 ) 2257 ) 2258); 2259header('Content-Type: application/xml'); 2260$dom->formatOutput = true; 2261print $dom->saveXML(); 2262?> 2263 ]]> 2264 </programlisting> 2265 'rootTag' was set to 'favorite_artists'. The resulting XML data therefore is: 2266 <programlisting role="tutorial"> 2267 <![CDATA[ 2268<?xml version="1.0" encoding="UTF-8"?> 2269<favorite_artists> 2270<artist> 2271 <name>Curtis Mayfield</name> 2272 <birth_year>1920</birth_year> 2273 <genre>Soul</genre> 2274</artist> 2275<artist> 2276 <name>Isaac Hayes</name> 2277 <birth_year>1942</birth_year> 2278 <genre>Soul</genre> 2279</artist> 2280<artist> 2281 <name>Ray Charles</name> 2282 <birth_year>1930</birth_year> 2283 <genre>Country and Soul</genre> 2284</artist> 2285</favorite_artists> 2286 ]]> 2287 </programlisting> 2288 </para> 2289 <para> 2290 Here goes an example with the rootTag being used at a lower level: 2291 <programlisting role="php"> 2292 <![CDATA[ 2293<?php 2294require_once 'XML/Query2XML.php'; 2295require_once 'MDB2.php'; 2296$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 2297$dom = $query2xml->getXML( 2298 "SELECT 2299 * 2300 FROM 2301 artist 2302 LEFT JOIN album ON album.artist_id = artist.artistid", 2303 array( 2304 'rootTag' => 'music_library', 2305 'rowTag' => 'artist', 2306 'idColumn' => 'artistid', 2307 'elements' => array( 2308 'artistid', 2309 'name', 2310 'birth_year', 2311 'birth_place', 2312 'genre', 2313 'albums' => array( 2314 'rootTag' => 'albums', 2315 'rowTag' => 'album', 2316 'idColumn' => 'albumid', 2317 'elements' => array( 2318 'albumid', 2319 'title', 2320 'published_year', 2321 'comment' 2322 ) 2323 ) 2324 ) 2325 ) 2326); 2327 2328header('Content-Type: application/xml'); 2329$dom->formatOutput = true; 2330print $dom->saveXML(); 2331?> 2332 ]]> 2333 </programlisting> 2334 ['elements']['albums']['rootTag'] is set to 'albums'. Therefore all 'album' tags of a single 2335 artist will be enclosed by a singel 'albums' tag. This example is actually taken from 2336 {@tutorial XML_Query2XML.pkg#casestudies.case02}, so please see there for the resulting XML data 2337 and further discussion. 2338 </para> 2339 <para> 2340 As shown in {@tutorial XML_Query2XML.pkg#casestudies.case04} is is also possible to assign 2341 an empty string to the rootTag option or to omit it at all. In our case this results in 2342 all the album tags not being surrounded by a single 'albums' tag but being directly placed 2343 inside the 'artist' tag: 2344 <programlisting role="php"> 2345 <![CDATA[ 2346<?php 2347require_once 'XML/Query2XML.php'; 2348require_once 'MDB2.php'; 2349$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 2350$dom = $query2xml->getXML( 2351 "SELECT 2352 * 2353 FROM 2354 artist 2355 LEFT JOIN album ON album.artist_id = artist.artistid", 2356 array( 2357 'rootTag' => 'music_library', 2358 'rowTag' => 'artist', 2359 'idColumn' => 'artistid', 2360 'elements' => array( 2361 'artistid', 2362 'name', 2363 'birth_year', 2364 'birth_place', 2365 'genre', 2366 'albums' => array( 2367 'rowTag' => 'album', 2368 'idColumn' => 'albumid', 2369 'elements' => array( 2370 'albumid', 2371 'title', 2372 'published_year', 2373 'comment' 2374 ) 2375 ) 2376 ) 2377 ) 2378); 2379 2380header('Content-Type: application/xml'); 2381$dom->formatOutput = true; 2382print $dom->saveXML(); 2383?> 2384 ]]> 2385 </programlisting> 2386 The resulting XML looks like this: 2387 <programlisting role="tutorial"> 2388 <![CDATA[ 2389<?xml version="1.0" encoding="UTF-8"?> 2390<music_library> 2391 <artist> 2392 <artistid>1</artistid> 2393 <name>Curtis Mayfield</name> 2394 <birth_year>1920</birth_year> 2395 <birth_place>Chicago</birth_place> 2396 <genre>Soul</genre> 2397 <album> 2398 <albumid>1</albumid> 2399 <title>New World Order</title> 2400 <published_year>1990</published_year> 2401 <comment>the best ever!</comment> 2402 </album> 2403 <album> 2404 <albumid>2</albumid> 2405 <title>Curtis</title> 2406 <published_year>1970</published_year> 2407 <comment>that man's got somthin' to say</comment> 2408 </album> 2409 </artist> 2410 <artist> 2411 <artistid>2</artistid> 2412 <name>Isaac Hayes</name> 2413 <birth_year>1942</birth_year> 2414 <birth_place>Tennessee</birth_place> 2415 <genre>Soul</genre> 2416 <album> 2417 <albumid>3</albumid> 2418 <title>Shaft</title> 2419 <published_year>1972</published_year> 2420 <comment>he's the man</comment> 2421 </album> 2422 </artist> 2423 <artist> 2424 <artistid>3</artistid> 2425 <name>Ray Charles</name> 2426 <birth_year>1930</birth_year> 2427 <birth_place>Mississippi</birth_place> 2428 <genre>Country and Soul</genre> 2429 </artist> 2430</music_library> 2431 ]]> 2432 </programlisting> 2433 <para> 2434 </para> 2435 Note however that a hidden child element is used as a container 2436 to ensure the order of the generated XML elements. Internally all elements with a name 2437 that starts with '__' are hidden. An explicit definition of the hidden complex element would look 2438 like this: 2439 <programlisting role="php"> 2440 <![CDATA[ 2441<?php 2442require_once 'XML/Query2XML.php'; 2443require_once 'MDB2.php'; 2444$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 2445$dom = $query2xml->getXML( 2446 "SELECT 2447 * 2448 FROM 2449 artist 2450 LEFT JOIN album ON album.artist_id = artist.artistid", 2451 array( 2452 'rootTag' => 'music_library', 2453 'rowTag' => 'artist', 2454 'idColumn' => 'artistid', 2455 'elements' => array( 2456 'artistid', 2457 'name', 2458 'birth_year', 2459 'birth_place', 2460 'genre', 2461 'albums' => array( 2462 'rootTag' => '__albums', 2463 'rowTag' => 'album', 2464 'idColumn' => 'albumid', 2465 'elements' => array( 2466 'albumid', 2467 'title', 2468 'published_year', 2469 'comment' 2470 ) 2471 ) 2472 ) 2473 ) 2474); 2475 2476header('Content-Type: application/xml'); 2477$dom->formatOutput = true; 2478print $dom->saveXML(); 2479?> 2480 ]]> 2481 </programlisting> 2482 </para> 2483 </refsect2> 2484 2485 <refsect2 id="{@id options_value}"> 2486 <title>$options['value']</title> 2487 <para> 2488 The value of an XML element's child text node. The specification can be of the following types: 2489 <itemizedlist> 2490 <listitem> 2491 <emphasis>COLUMN NAME</emphasis>: this is the default if not preceeded by ':' or '#'. 2492 If the column does not exist, an {@link XML_Query2XML_ConfigException} will be thrown. 2493 </listitem> 2494 <listitem> 2495 <emphasis>STATIC TEXT with a : prefix</emphasis>: if the value is preceeded by a colon (':'), it is 2496 interpreted as static text. 2497 </listitem> 2498 <listitem> 2499 <emphasis>CALLBACK FUNCTION with a # prefix</emphasis>: if the value is preceeded by a pound sign ('#'), it 2500 is interpreted as a callback function. You can use a regular function (e.g. '#myFunction()') or a static 2501 method (e.g. '#MyClass::myFunction()') - for how to use a non-static method, see the type COMMAND OBJECT. 2502 The current record will be passed to the callback function 2503 as an associative array. You can also pass additional string arguments to the callback function by specifing 2504 them within the opening and closing brace; e.g. '#Utils::limit(12)' will result in Util::limit() being 2505 called with the current record as the first and '12' as the second argument. If you do not want 2506 to pass additional arguments to the callback function, the opening and closing brace are optional. 2507 The callback function's return value will be converted to a string and used as the child text node 2508 if it is anything but an object or an array. If you do return an object or an array from a callback 2509 function it has to be an instance of {@link http://www.php.net/manual/en/ref.dom.php DOMNode} or 2510 an array of DOMNode instances. Please see {@tutorial XML_Query2XML.pkg#other_xml_data_sources} for examples 2511 and further details. If an instances of any other class is returned, a XML_Query2XML_XMLException will be thrown. 2512 </listitem> 2513 <listitem> 2514 <emphasis>COMMAND OBJECT (object)</emphasis>: If you want to use a non-static method as a 2515 callback function, you can do so by specifying the value as an instance of a class that 2516 implements the {@link XML_Query2XML_Callback} interface. This implementation of the 2517 {@link http://en.wikipedia.org/wiki/Command_pattern command pattern} gives you all the 2518 flexibility. The disadvantage ist that you cannot use the XML UNSERIALIZATION prefix 2519 or the CONDITIONAL prefix. Note: you have to require_once 'XML/Query2XML/Callback.php' 2520 before using the XML_Query2XML_Callback interface. The return value of a COMMAND OBJECT's 2521 execute() method is treated exactly the same as the return value of a CALLBACK FUNCTION. 2522 </listitem> 2523 </itemizedlist> 2524 There are four more prefixes available that can be used in conjunction with all the prifixes described above: 2525 <itemizedlist> 2526 <listitem> 2527 <emphasis>XML UNSERIALIZATION prefix &</emphasis>: the ampersand (&) prefix allows you to 2528 automatically unserialize string data, i.e. transform a string into a 2529 {@link http://www.php.net/manual/en/ref.dom.php#dom.class.domdocument DOMDocument}. DOMDocument's 2530 {@link http://www.php.net/manual/en/function.dom-domdocument-loadxml.php loadXML()} method will be 2531 used for this purpose. You can combine all three types with this prefix: '&COLUMN_NAME', 2532 '&#function()' or '&:<name>John</name>' will all work. You can even use the CONDITIONAL prefix 2533 which has to preceed all other prefixes. If the data cannot be unserialized i.e. 2534 {@link http://www.php.net/manual/en/function.dom-domdocument-loadxml.php DOMDocument::loadXML()} 2535 returns false, a XML_Query2XML_XMLException will be thrown. Please see 2536 {@tutorial XML_Query2XML.pkg#other_xml_data_sources} for examples and further 2537 details. 2538 </listitem> 2539 <listitem> 2540 <emphasis>BASE64 ENCODING prefix ^</emphasis>: if the specification starts with a carrat sign ('^'), 2541 the element value will be passed to {@link http://www.php.net/base64_encode base64_encode()}. 2542 The BASE64 ENCODING prefix can be used with all the prefixes described above (just put the BASE64 ENCODING prefix first): 2543 e.g. '^#', '^:' or '^COLUMN_NAME'. 2544 </listitem> 2545 <listitem> 2546 <emphasis>CDATA SECTION prefix =</emphasis>: if the specification starts with an equal sign ('='), 2547 the element value will be enclosed in a CDATA section. A CDATA section starts with 2548 "<![CDATA[" and ends with "]]>". 2549 The CDATA SECTION prefix can be used with all the prefixes described above (just put the CDATA SECTION prefix first): 2550 e.g. '=#', '=:', '=COLUMN_NAME' or '=^'. 2551 </listitem> 2552 <listitem> 2553 <emphasis>CONDITIONAL prefix ?</emphasis>: if the specification starts with a question mark ('?'), 2554 the whole element will be skipped if the value equals (==) an empty string. The CONDITIONAL prefix 2555 can be combined with all types described above: if you do this you have to write the CONDITIONAL 2556 prefix first e.g. '?#', '?:', '?&', '?=', '?^', or '?COLUMN_NAME'. 2557 </listitem> 2558 </itemizedlist> 2559 Note: for ovious reasons, the XML UNSERIALIZATION prefix and the CONDITIONAL prefix cannot be 2560 combined with a COMMAND OBJECT. 2561 </para> 2562 <para> 2563 Basically, the same syntax can be use for 2564 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications}, 2565 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes.simple_attribute_specifications}, 2566 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql.complexqueryspecification} and 2567 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_idcolumn} because the private method 2568 {@link XML_Query2XML::_applyColumnStringToRecord()} is used in all cases. 2569 </para> 2570 <para> 2571 The following example demonstrates the usage of some of the types (for a full demonstration of all types 2572 see the second example under {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications}). 2573 The comment element will be skipped if its value == "". 2574 Same holds true for the genre element which uses the trim'ed version of the value stored in the genre column. The comment 2575 tag has an attribute named type with a static value of "short text". The published_century element gets the century 2576 calculated using floor and has the attribute digitCount with a static value of 2. 2577 <programlisting role="php"> 2578 <![CDATA[ 2579<?php 2580require_once 'XML/Query2XML.php'; 2581require_once 'MDB2.php'; 2582$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 2583$dom = $query2xml->getXML( 2584 "SELECT 2585 * 2586 FROM 2587 album al, 2588 artist ar 2589 WHERE 2590 al.artist_id = ar.artistid", 2591 array( 2592 'rootTag' => 'albums', 2593 'idColumn' => 'albumid', 2594 'rowTag' => 'album', 2595 'elements' => array( 2596 'albumid', 2597 'title', 2598 'published_year', 2599 'published_century' => array( 2600 'value' => "#Utils::getPublishedYearCentury()", 2601 'attributes' => array( 2602 'digitCount' => ':2' 2603 ) 2604 ), 2605 'comment' => array( 2606 'value' => '?comment', 2607 'attributes' => array( 2608 'type' => ':short text' 2609 ) 2610 ), 2611 'genre' => array( 2612 'value' => "?#Utils::trimGenre()" 2613 ) 2614 ) 2615 ) 2616); 2617header('Content-Type: application/xml'); 2618$dom->formatOutput = true; 2619print $dom->saveXML(); 2620 2621class Utils 2622{ 2623 function trimGenre($record) 2624 { 2625 return trim($record['genre']); 2626 } 2627 2628 function getPublishedYearCentury($record) 2629 { 2630 return floor($record['published_year']/100); 2631 } 2632} 2633?> 2634 ]]> 2635 </programlisting> 2636 The resulting XML data looks like this: 2637 <programlisting role="tutorial"> 2638 <![CDATA[ 2639<?xml version="1.0" encoding="UTF-8"?> 2640<albums> 2641 <album> 2642 <albumid>1</albumid> 2643 <title>New World Order</title> 2644 <published_year>1990</published_year> 2645 <published_century digitCount="2">19</published_century> 2646 <comment type="short text">the best ever!</comment> 2647 <genre>Soul</genre> 2648 </album> 2649 <album> 2650 <albumid>2</albumid> 2651 <title>Curtis</title> 2652 <published_year>1970</published_year> 2653 <published_century digitCount="2">19</published_century> 2654 <comment type="short text">that man's got somthin' to say</comment> 2655 <genre>Soul</genre> 2656 </album> 2657 <album> 2658 <albumid>3</albumid> 2659 <title>Shaft</title> 2660 <published_year>1972</published_year> 2661 <published_century digitCount="2">19</published_century> 2662 <comment type="short text">he's the man</comment> 2663 <genre>Soul</genre> 2664 </album> 2665</albums> 2666 ]]> 2667 </programlisting> 2668 </para> 2669 </refsect2> 2670 2671 <refsect2 id="{@id options_condition}"> 2672 <title>$options['condition']</title> 2673 <para> 2674 This option allows you to specify a condition for the element to be included. 2675 The string assigned to the condition option can be of the following types: 2676 <itemizedlist> 2677 <listitem> 2678 <emphasis>COLUMN NAME</emphasis>: this is the default if not preceeded by ':' or '#'. 2679 If the column does not exist, an {@link XML_Query2XML_ConfigException} will be thrown. 2680 Remember that the string '0' or '' will both evaluate to false which 2681 means that the element would be skipped. Note: in most cases you will be much 2682 better off changing your WHERE clause than using this type of condition. 2683 </listitem> 2684 <listitem> 2685 <emphasis>STATIC TEXT with a : prefix</emphasis>: if the value is preceeded by a colon (':'), it is 2686 interpreted as static text. Remember that the string '0' or '' will both evaluate to false which 2687 means that the element would be skipped. 2688 </listitem> 2689 <listitem> 2690 <emphasis>CALLBACK FUNCTION with a # prefix</emphasis>: if the value is preceeded by a pound sign ('#'), it 2691 is interpreted as a callback function. You can use a regular function (e.g. '#myFunction()') or a static 2692 method (e.g. '#MyClass::myFunction()') - for how to use a non-static method, see the type COMMAND OBJECT. 2693 The current record will be passed to the callback function 2694 as an associative array. You can also pass additional string arguments to the callback function by specifing 2695 them within the opening and closing brace; e.g. '#Utils::limit(12)' will result in Util::limit() being 2696 called with the current record as the first and '12' as the second argument. If you do not want 2697 to pass additional arguments to the callback function, the opening and closing brace are optional. 2698 </listitem> 2699 <listitem> 2700 <emphasis>COMMAND OBJECT (object)</emphasis>: If you want to use a non-static method as a 2701 callback function, you can do so by specifying the value as an instance of a class that 2702 implements the {@link XML_Query2XML_Callback} interface. This implementation of the 2703 {@link http://en.wikipedia.org/wiki/Command_pattern command pattern} gives you all the 2704 flexibility. Note: you have to require_once 'XML/Query2XML/Callback.php' 2705 before using the XML_Query2XML_Callback interface. The return value of a COMMAND OBJECT's 2706 execute() method is treated exactly the same as the return value of a CALLBACK FUNCTION. 2707 </listitem> 2708 </itemizedlist> 2709 2710 This option provides a similar function as the "?" prefix for column specifications - see 2711 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications}, 2712 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes.simple_attribute_specifications} and {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_value}. 2713 The difference is that $options['condition'] is more powerful: you can call any external function you like 2714 to determin whether the element shall be included. 2715 Here goes a little example: 2716 <programlisting role="php"> 2717 <![CDATA[ 2718<?php 2719if (isset($_REQUEST['includeCondition'])) { 2720 $includeCondition = ($_REQUEST['includeCondition'] == '1'); 2721} else { 2722 $includeCondition = false; 2723} 2724require_once 'XML/Query2XML.php'; 2725require_once 'MDB2.php'; 2726$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 2727$dom =& $query2xml->getXML( 2728 "SELECT 2729 * 2730 FROM 2731 artist 2732 LEFT JOIN album ON album.artist_id = artist.artistid", 2733 array( 2734 'rootTag' => 'music_library', 2735 'rowTag' => 'artist', 2736 'idColumn' => 'artistid', 2737 'elements' => array( 2738 'artistid', 2739 'name', 2740 'birth_year', 2741 'birth_place', 2742 'genre', 2743 'albums' => array( 2744 'rootTag' => 'albums', 2745 'rowTag' => 'album', 2746 'idColumn' => 'albumid', 2747 'condition' => '#isSpecialPublishedYear()', 2748 'elements' => array( 2749 'albumid', 2750 'title', 2751 'published_year', 2752 'comment' => array( 2753 'value' => 'comment', 2754 'condition' => ':' . ($includeCondition ? '1' : '0') 2755 ) 2756 ) 2757 ) 2758 ) 2759 ) 2760); 2761header('Content-Type: application/xml'); 2762$dom->formatOutput = true; 2763print $dom->saveXML(); 2764 2765/**Returns whether $year is 1970 or 1972. 2766*/ 2767function isSpecialPublishedYear($record) 2768{ 2769 //do some highly complex calculations ... 2770 return $record['published_year'] == 1970 || $record['published_year'] == 1972; 2771} 2772?> 2773 ]]> 2774 </programlisting> 2775 The resulting XML data is: 2776 <programlisting role="tutorial"> 2777 <![CDATA[ 2778<?xml version="1.0" encoding="UTF-8"?> 2779<music_library> 2780 <artist> 2781 <artistid>1</artistid> 2782 <name>Curtis Mayfield</name> 2783 <birth_year>1920</birth_year> 2784 <birth_place>Chicago</birth_place> 2785 <genre>Soul</genre> 2786 <albums> 2787 <album> 2788 <albumid>2</albumid> 2789 <title>Curtis</title> 2790 <published_year>1970</published_year> 2791 </album> 2792 </albums> 2793 </artist> 2794 <artist> 2795 <artistid>2</artistid> 2796 <name>Isaac Hayes</name> 2797 <birth_year>1942</birth_year> 2798 <birth_place>Tennessee</birth_place> 2799 <genre>Soul</genre> 2800 <albums> 2801 <album> 2802 <albumid>3</albumid> 2803 <title>Shaft</title> 2804 <published_year>1972</published_year> 2805 </album> 2806 </albums> 2807 </artist> 2808 <artist> 2809 <artistid>3</artistid> 2810 <name>Ray Charles</name> 2811 <birth_year>1930</birth_year> 2812 <birth_place>Mississippi</birth_place> 2813 <genre>Country and Soul</genre> 2814 <albums /> 2815 </artist> 2816</music_library> 2817 ]]> 2818 </programlisting> 2819 </para> 2820 <para> 2821 Note that (if present) {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql} will 2822 get processed *before* evaluating the condition. This allows you to wirte code 2823 like the following: 2824 <programlisting role="php"> 2825 <![CDATA[ 2826<?php 2827require_once 'XML/Query2XML.php'; 2828require_once 'MDB2.php'; 2829$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 2830$dom = $query2xml->getXML( 2831 "SELECT 2832 * 2833 FROM 2834 artist", 2835 array( 2836 'rootTag' => 'artists', 2837 'idColumn' => 'artistid', 2838 'rowTag' => 'artist', 2839 'elements' => array( 2840 'artistid', 2841 'name', 2842 'albums' => array( 2843 'idColumn' => 'albumid', 2844 'sql' => array( 2845 'data' => array( 2846 'artistid' 2847 ), 2848 'query' => "SELECT * FROM album WHERE artist_id = ?", 2849 ), 2850 'condition' => '#isGT1980()', 2851 'elements' => array( 2852 'title', 2853 'published_year' 2854 ) 2855 ) 2856 ) 2857 ) 2858); 2859header('Content-Type: application/xml'); 2860$dom->formatOutput = true; 2861print $dom->saveXML(); 2862 2863function isGT1980($record) 2864{ 2865 return $record['published_year'] > 1980; 2866} 2867?> 2868 ]]> 2869 </programlisting> 2870 "published_year" is a column of the table album but as the "sql" option is processed before evaluating 2871 the "condition" option everything works just fine: 2872 <programlisting role="tutorial"> 2873 <![CDATA[ 2874<?xml version="1.0" encoding="UTF-8"?> 2875<artists> 2876 <artist> 2877 <artistid>1</artistid> 2878 <name>Curtis Mayfield</name> 2879 <albums> 2880 <title>New World Order</title> 2881 <published_year>1990</published_year> 2882 </albums> 2883 </artist> 2884 <artist> 2885 <artistid>2</artistid> 2886 <name>Isaac Hayes</name> 2887 </artist> 2888 <artist> 2889 <artistid>3</artistid> 2890 <name>Ray Charles</name> 2891 </artist> 2892</artists> 2893 ]]> 2894 </programlisting> 2895 </para> 2896 </refsect2> 2897 2898 <refsect2 id="{@id options_sql}"> 2899 <title>$options['sql']</title> 2900 <para> 2901 <emphasis>Note:</emphasis> This option is driver-specific. The following discussion is limited 2902 to the database-related drivers. 2903 </para> 2904 <para> 2905 This and {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options} are the only options 2906 that can only be present within 2907 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.complex_element_specifications}. 2908 If given at the root level, it would be just ignored. $options['sql'] allows you to split 2909 up one huge JOIN into multiple smaller queries. You might want (or have) to do this in 2910 several scenarios: 2911 <itemizedlist> 2912 <listitem> 2913 Your RDBMS has a maximum number of fields it can return in a single query and you've reached it. 2914 </listitem> 2915 <listitem> 2916 You are short on memory: let's say your big JOIN returns 100 fields and you have 10 000 records. 2917 It might turn out that the memory consumption is lower if you split up the single big JOIN into 2918 multiple quieres that have smaller result sets. As all the data won't be in memory at once, 2919 it might even run faster. 2920 </listitem> 2921 <listitem> 2922 You are too lazy to think about how to best join these 8 tables :) 2923 </listitem> 2924 </itemizedlist> 2925 You will definitively want to do some {@tutorial XML_Query2XML.pkg#profiling} before deciding whether 2926 or not to split up one big JOIN into multiple smaller JOINs. 2927 </para> 2928 <para> 2929 There are two ways of specifying $options['sql']: 2930 <itemizedlist> 2931 <listitem> 2932 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql.simplequeryspecification}: uses the query() method provided by the database abstraction layer (PDO/MDB2/DB/ADOdb) - use it with care 2933 </listitem> 2934 <listitem> 2935 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql.complexqueryspecification}: uses 2936 the prepare() and execute() methods provided by the database abstraction layer and 2937 can therefore prevent SQL injection and is also faster in most scenarios 2938 </listitem> 2939 </itemizedlist> 2940 </para> 2941 <refsect3 id="{@id simplequeryspecification}"> 2942 <title>Simple Query Specification</title> 2943 <para> 2944 Since v0.8.0 a simple query specifications are purely static strings (in most cases 2945 you will want to use a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql.complexqueryspecification}): 2946 <programlisting role="php"> 2947 <![CDATA[ 2948<?php 2949require_once 'XML/Query2XML.php'; 2950require_once 'MDB2.php'; 2951$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 2952$dom = $query2xml->getXML( 2953 "SELECT 2954 * 2955 FROM 2956 artist 2957 WHERE 2958 artistid = 1", 2959 array( 2960 'rootTag' => 'music_library', 2961 'rowTag' => 'artist', 2962 'idColumn' => 'artistid', 2963 'elements' => array( 2964 'artistid', 2965 'name', 2966 'birth_year', 2967 'birth_place', 2968 'genre', 2969 'albums' => array( 2970 'sql' => 'SELECT * FROM album WHERE artist_id = 1', 2971 'rootTag' => 'albums', 2972 'rowTag' => 'album', 2973 'idColumn' => 'albumid', 2974 'elements' => array( 2975 'albumid', 2976 'title', 2977 'published_year', 2978 'comment' 2979 ) 2980 ) 2981 ) 2982 ) 2983); 2984 2985header('Content-Type: application/xml'); 2986$dom->formatOutput = true; 2987print $dom->saveXML(); 2988?> 2989 ]]> 2990 </programlisting> 2991 </para> 2992 <para> 2993 To understand how $options['sql'] really works, some knowledge of XML_Query2XML's internals might be helpful: 2994 {@link XML_Query2XML::getXML()} calls the private method {@link XML_Query2XML::_getNestedXMLRecord()} for 2995 every record retrieved from the database using the SQL statement passed to getXML() as first 2996 argument. XML_Query2XML::_getNestedXMLRecord() will then process the current record according 2997 to the settings specified in $options. The processing of all 2998 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.complex_element_specifications} 2999 is handed off to the private method {@link XML_Query2XML::_processComplexElementSpecification()}. 3000 XML_Query2XML::_processComplexElementSpecification() in turn will call the private method 3001 {@link XML_Query2XML::_applySqlOptionsToRecord()} to interpret $options['sql'] and {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options}. 3002 XML_Query2XML::_processComplexElementSpecification() will then call again {@link XML_Query2XML::_getNestedXMLRecord()} for 3003 every record retrieved using the query specified in the 'sql' option. 3004 </para> 3005 </refsect3> 3006 <refsect3 id="{@id complexqueryspecification}"> 3007 <title>Complex Query Specification</title> 3008 <para> 3009 A Complex Query Specification uses the database abstraction layer's prepare() and execute() methods 3010 and therefore prevents SQL injection and is also faster 3011 than a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql.simplequeryspecification} in most scenarios. 3012 It can consist of multiple parts (only $options['sql']['query'] is mandatory): 3013 <itemizedlist> 3014 <listitem> 3015 <emphasis>$options['sql']['query']</emphasis>: the SQL query as a string that contains a placeholder 3016 for each element of $options['sql']['data']. 3017 </listitem> 3018 <listitem> 3019 <emphasis>$options['sql']['driver']</emphasis>: allows you to use a different XML_Query2XML_Driver 3020 for this complex query than the one passed to 3021 {@tutorial XML_Query2XML.pkg#query2xml_factory}. Please see 3022 {@tutorial XML_Query2XML.pkg#multipledrivers} for details. $options['sql']['driver'] is optional. 3023 </listitem> 3024 <listitem> 3025 <emphasis>$options['sql']['limit']</emphasis>: allows you to limit the number of records returned from the 3026 query. It has to be a numeric value. Please note that a value of 0 (or '0') is equivalent to not 3027 setting $options['sql']['limit'] at all. $options['sql']['limit'] and $options['sql']['offset'] are 3028 only interpreted by the drivers for PEAR MDB2 and PEAR DB. All other drivers simply ignore these two 3029 options. 3030 </listitem> 3031 <listitem> 3032 <emphasis>$options['sql']['offset']</emphasis>: allows you to set the number of the first record 3033 to retrieve. This has to be a numeric value. The default is 0. Please note that this option will 3034 be ignored unless $options['sql']['limit'] is set. $options['sql']['offset'] and 3035 $options['sql']['limit'] are only interpreted by the drivers for PEAR MDB2 and PEAR DB. 3036 All other drivers simply ignore these two options. 3037 </listitem> 3038 <listitem> 3039 <emphasis>$options['sql']['data']</emphasis>: an indexed array of values. This is optional. 3040 The specification can be of the following types: 3041 <itemizedlist> 3042 <listitem> 3043 <emphasis>COLUMN NAME</emphasis>: this is the default if not preceeded by ':' or '#'. 3044 If the column does not exist, an {@link XML_Query2XML_ConfigException} will be thrown. Note 3045 that the parent record will be used! This is quite logic as this SQL statement has not been executed yet :) 3046 </listitem> 3047 <listitem> 3048 <emphasis>STATIC TEXT with a : prefix</emphasis>: if the value is preceeded by a colon (':'), it is 3049 interpreted as static text. 3050 </listitem> 3051 <listitem> 3052 <emphasis>CALLBACK FUNCTION with a # prefix</emphasis>: if the value is preceeded by a pound sign ('#'), it 3053 is interpreted as a callback function. You can use a regular function (e.g. '#myFunction()') or a static 3054 method (e.g. '#MyClass::myFunction()') - for how to use a non-static method, see the type COMMAND OBJECT. 3055 The current record will be passed to the callback function 3056 as an associative array. You can also pass additional string arguments to the callback function by specifing 3057 them within the opening and closing brace; e.g. '#Utils::limit(12)' will result in Util::limit() being 3058 called with the current record as the first and '12' as the second argument. If you do not want 3059 to pass additional arguments to the callback function, the opening and closing brace are optional. 3060 </listitem> 3061 <listitem> 3062 <emphasis>COMMAND OBJECT (object)</emphasis>: If you want to use a non-static method as a 3063 callback function, you can do so by specifying the value as an instance of a class that 3064 implements the {@link XML_Query2XML_Callback} interface. This implementation of the 3065 {@link http://en.wikipedia.org/wiki/Command_pattern command pattern} gives you all the 3066 flexibility. Note: you have to require_once 'XML/Query2XML/Callback.php' 3067 before using the XML_Query2XML_Callback interface. The return value of a COMMAND OBJECT's 3068 execute() method is treated exactly the same as the return value of a CALLBACK FUNCTION. 3069 </listitem> 3070 </itemizedlist> 3071 The same syntax (with the additional '?' prefix) can be use for 3072 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications}, 3073 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_value}, 3074 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes.simple_attribute_specifications} and 3075 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_idcolumn} because the private method 3076 {@link XML_Query2XML::_applyColumnStringToRecord()} is used in all cases. 3077 <emphasis>Note:</emphasis> $options['sql']['data'] is optional! 3078 </listitem> 3079 </itemizedlist> 3080 Here is a simple example similar to {@tutorial XML_Query2XML.pkg#casestudies.case03}: 3081 </para> 3082 <para> 3083 <programlisting role="php"> 3084 <![CDATA[ 3085<?php 3086require_once 'XML/Query2XML.php'; 3087require_once 'MDB2.php'; 3088$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 3089$dom = $query2xml->getXML( 3090 "SELECT 3091 * 3092 FROM 3093 artist", 3094 array( 3095 'rootTag' => 'music_library', 3096 'rowTag' => 'artist', 3097 'idColumn' => 'artistid', 3098 'elements' => array( 3099 'artistid', 3100 'name', 3101 'birth_year', 3102 'birth_place', 3103 'genre', 3104 'albums' => array( 3105 'sql' => array( 3106 'data' => array( 3107 'artistid' 3108 ), 3109 'query' => "SELECT * FROM album WHERE artist_id = ?" 3110 ), 3111 'rootTag' => 'albums', 3112 'rowTag' => 'album', 3113 'idColumn' => 'albumid', 3114 'elements' => array( 3115 'albumid', 3116 'title', 3117 'published_year', 3118 'comment' 3119 ) 3120 ) 3121 ) 3122 ) 3123); 3124 3125header('Content-Type: application/xml'); 3126$dom->formatOutput = true; 3127print $dom->saveXML(); 3128?> 3129 ]]> 3130 </programlisting> 3131 </para> 3132 </refsect3> 3133 </refsect2> 3134 3135 <refsect2 id="{@id options_sql_options}"> 3136 <title>$options['sql_options']</title> 3137 <para> 3138 This allows you to specify how {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql} is 3139 handled. $options['sql_options'] is an associative array that can have the following fileds: 3140 <itemizedlist> 3141 <listitem> 3142 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options.cached} 3143 </listitem> 3144 <listitem> 3145 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options.single_record} 3146 </listitem> 3147 <listitem> 3148 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options.merge} 3149 </listitem> 3150 <listitem> 3151 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options.merge_selective} 3152 </listitem> 3153 <listitem> 3154 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options.merge_master} 3155 </listitem> 3156 </itemizedlist> 3157 Per default all options are set to the boolean value false. 3158 </para> 3159 <refsect3 id="{@id cached}"> 3160 <title>$options['sql_options']['cached']</title> 3161 <para> 3162 Since 1.5.0RC1 Caching is deactivated by default. 3163 If caching is activated the result of a query is stored in the private associative array 3164 {@link XML_Query2XML::$_recordCache} using the SQL query string as key. If the exact same 3165 query needs to be executed a second time, its results can be retrieved from cache. 3166 </para> 3167 <para> 3168 Before setting $options['sql_options']['cached'] to true, do some 3169 {@tutorial XML_Query2XML.pkg#profiling}. As documented in 3170 {@tutorial XML_Query2XML.pkg#profiling.getprofile} the CACHED column in the profile output will 3171 show 'true!' if caching is performed without being necessary. 3172 </para> 3173 <para> 3174 Caching only makes sense, if you have to run exactly the same query multiple times. 3175 </para> 3176 </refsect3> 3177 <refsect3 id="{@id single_record}"> 3178 <title>$options['sql_options']['single_record']</title> 3179 <para> 3180 Use this option to make sure that the SQL query you specified in 3181 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql} returns only a single record. 3182 This option is in fact of limited use. Do not use it to fetch only the first record 3183 from a large result set. (SQL is your friend: use a better WHERE clause!) 3184 </para> 3185 </refsect3> 3186 <refsect3 id="{@id merge}"> 3187 <title>$options['sql_options']['merge']</title> 3188 <para> 3189 By default no merging is done so that less memory is used. This means that the data of the record 3190 present on the parent level will not be available at this level. Only the data returned by 3191 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql} will be available (and therefore 3192 use up memory). If you also need the data of the record present on the parent level the two arrays 3193 have to be merged using {@link http://www.php.net/array_merge array_merge()}. If 3194 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql} returned multiple records, each of them 3195 has to be merged with the one of the parent level separatly: 3196 <programlisting role="php"> 3197 <![CDATA[ 3198<?php 3199require_once 'XML/Query2XML.php'; 3200require_once 'MDB2.php'; 3201$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 3202$dom = $query2xml->getXML( 3203 "SELECT 3204 * 3205 FROM 3206 artist", 3207 array( 3208 'rootTag' => 'MUSIC_LIBRARY', 3209 'rowTag' => 'ARTIST', 3210 'idColumn' => 'artistid', 3211 'elements' => array( 3212 'NAME' => 'name', 3213 'BIRTH_YEAR' => 'birth_year', 3214 'GENRE' => 'genre', 3215 'albums' => array( 3216 'sql' => array( 3217 'data' => array('artistid'), 3218 'query' => "SELECT * FROM album WHERE artist_id = ?" 3219 ), 3220 'sql_options' => array( 3221 'merge' => true 3222 ), 3223 'rootTag' => '', 3224 'rowTag' => 'ALBUM', 3225 'idColumn' => 'albumid', 3226 'elements' => array( 3227 'TITLE' => 'title', 3228 'PUBLISHED_YEAR' => 'published_year', 3229 'COMMENT' => 'comment', 3230 'GENRE' => 'genre' 3231 ) 3232 ) 3233 ) 3234 ) 3235); 3236 3237header('Content-Type: application/xml'); 3238$dom->formatOutput = true; 3239print $dom->saveXML(); 3240?> 3241 ]]> 3242 </programlisting> 3243 This produces quite some overhead. It is therefore highly recommended to use 3244 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options.merge_selective} 3245 described in the next section. 3246 </para> 3247 </refsect3> 3248 <refsect3 id="{@id merge_selective}"> 3249 <title>$options['sql_options']['merge_selective']</title> 3250 <para> 3251 As a full merge with the parent record might severly affect the performance, the sql option 3252 merge_selective allows you to only merge the current record with specific columns of the 3253 parent record. Just place the names of all columns of the parent record you want to be 3254 available in the current record in an array and assign it to the merge_selective option. 3255 Here goes an example: 3256 <programlisting role="php"> 3257 <![CDATA[ 3258<?php 3259require_once 'XML/Query2XML.php'; 3260require_once 'MDB2.php'; 3261$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 3262$dom = $query2xml->getXML( 3263 "SELECT 3264 * 3265 FROM 3266 artist", 3267 array( 3268 'rootTag' => 'MUSIC_LIBRARY', 3269 'rowTag' => 'ARTIST', 3270 'idColumn' => 'artistid', 3271 'elements' => array( 3272 'NAME' => 'name', 3273 'BIRTH_YEAR' => 'birth_year', 3274 'GENRE' => 'genre', 3275 'albums' => array( 3276 'sql' => array( 3277 'data' => array('artistid'), 3278 'query' => "SELECT * FROM album WHERE artist_id = ?" 3279 ), 3280 'sql_options' => array( 3281 'merge_selective' => array('genre') 3282 ), 3283 'rootTag' => '', 3284 'rowTag' => 'ALBUM', 3285 'idColumn' => 'albumid', 3286 'elements' => array( 3287 'TITLE' => 'title', 3288 'PUBLISHED_YEAR' => 'published_year', 3289 'COMMENT' => 'comment', 3290 'GENRE' => 'genre' 3291 ) 3292 ) 3293 ) 3294 ) 3295); 3296 3297header('Content-Type: application/xml'); 3298$dom->formatOutput = true; 3299print $dom->saveXML(); 3300?> 3301 ]]> 3302 </programlisting> 3303 Please see {@tutorial XML_Query2XML.pkg#casestudies.case04} for a similar example and 3304 more discussion of {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options.merge_selective}. 3305 </para> 3306 </refsect3> 3307 <refsect3 id="{@id merge_master}"> 3308 <title>$options['sql_options']['merge_master']</title> 3309 <para> 3310 If (selective) merging is performed, it might become important which record overwrites the data of the other. 3311 As soon as both result sets have a column with the same name, there is a confilict that has to 3312 be resolved. By default, the record of the parent level is the master and overwrites the 3313 record(s) returned by {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql}. If you want 3314 the new records to overwrite the record of the parent level, set 3315 $options['sql_options']['merge_master'] to true. Note that this option only has an effect if 3316 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options.merge} is set to true or 3317 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options.merge_selective} is used. 3318 </para> 3319 </refsect3> 3320 </refsect2> 3321 3322 <refsect2 id="{@id options_mapper}"> 3323 <title>$options['mapper']</title> 3324 <para> 3325 This option allows you to specifiy a function for mapping SQL identifiers to XML names. 3326 Whenever you use a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications Simple Element Specification} 3327 or a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes.simple_attribute_specifications Simple Attribute Specification} 3328 only with a column name and without a tag/attribute name, the specified column name will be used for the 3329 tag/attribute name. Please note that mapping is also performed when the 3330 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.asterisk_shortcut} is used. 3331 Per default $options['mapper'] is set to false which means that no special mapping is used. 3332 $options['mapper'] can have one of the following formats: 3333 <itemizedlist> 3334 <listitem> 3335 <emphasis>'CLASS::STATIC_METHOD'</emphasis>: this syntax allows you to use a static method 3336 for mapping: 3337 <programlisting role="php"> 3338 <![CDATA[ 3339'mapper' => 'MyMapper::map' 3340 ]]> 3341 </programlisting> 3342 </listitem> 3343 <listitem> 3344 <emphasis>array('CLASS', 'STATIC_METHOD')</emphasis>: this syntax also allows you to use a static 3345 method for mapping: 3346 <programlisting role="php"> 3347 <![CDATA[ 3348'mapper' => array('MyMapper', 'map') 3349 ]]> 3350 </programlisting> 3351 </listitem> 3352 <listitem> 3353 <emphasis>array($instance, 'METHOD')</emphasis>: this syntax allows you to use a non-static 3354 method for mapping: 3355 <programlisting role="php"> 3356 <![CDATA[ 3357'mapper' => array($myMap, 'map') 3358 ]]> 3359 </programlisting> 3360 </listitem> 3361 <listitem> 3362 <emphasis>'FUNCTION'</emphasis>: this syntax allows you to use a regular function 3363 for mapping: 3364 <programlisting role="php"> 3365 <![CDATA[ 3366'mapper' => 'myUppercaseMapper' 3367 ]]> 3368 </programlisting> 3369 </listitem> 3370 <listitem> 3371 <emphasis>false</emphasis>: use the boolean value false (or any other value that == false) to 3372 deactivate any special mapping: 3373 <programlisting role="php"> 3374 <![CDATA[ 3375'mapper' => false 3376 ]]> 3377 </programlisting> 3378 </listitem> 3379 </itemizedlist> 3380 Remember that the mapping only applies to 3381 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications} 3382 and {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes.simple_attribute_specifications} 3383 that do not explicitly have a tag/attribute name or those that have a tag/attribute name that contains 3384 an {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.asterisk_shortcut asterisk shortcut}. 3385 The following example will also show that a mapper defined at the root level is also used at all lower levels 3386 (unless it gets overwritten, see 3387 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_mapper.multiple_mappers}): 3388 <programlisting role="php"> 3389 <![CDATA[ 3390<?php 3391class SomeMapper 3392{ 3393 public function map($str) 3394 { 3395 //do something with $str 3396 return $str; 3397 } 3398} 3399 3400require_once 'XML/Query2XML.php'; 3401require_once 'XML/Query2XML/ISO9075Mapper.php'; 3402require_once 'MDB2.php'; 3403$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 3404$dom = $query2xml->getXML( // 3405 "SELECT * FROM artist", // 3406 array( // 3407 'rootTag' => 'favorite_artists', //no mapping 3408 'idColumn' => 'artistid', //nothing to map 3409 'rowTag' => 'artist', //no mapping 3410 'mapper' => 'SomeMapper::map', // 3411 'elements' => array( // 3412 'artistid', //mapping 3413 'NAME' => 'name', //no mapping as the tag name is specified 3414 '*', //mapping 3415 'TAG_*' => '*', //does a mapping too! 3416 'albums' => array( //nothing to map 3417 'sql' => array( // 3418 'data' => array( // 3419 'artistid' //nothing to map 3420 ), // 3421 'query' => 'SELECT * FROM album WHERE artist_id = ?' // 3422 ), // 3423 'rootTag' => 'albums', //no mapping 3424 'rowTag' => 'album', //no mapping 3425 'idColumn' => 'albumid',//nothing to map 3426 'elements' => array( // 3427 'albumid', //mapping using the mapper specified at the root level 3428 'title', //mapping using the mapper specified at the root level 3429 'published_year', //mapping using the mapper specified at the root level 3430 'comment' //mapping using the mapper specified at the root level 3431 ) // 3432 ) // 3433 ), // 3434 'attributes' => array( // 3435 'artistid', //mapping 3436 'NAME' => 'name', //no mapping as the tag name is specified 3437 '*', //mapping 3438 'TAG_*' => '*' //does a mapping too! 3439 ) // 3440 ) // 3441); // 3442header('Content-Type: application/xml'); // 3443print $dom->saveXML(); // 3444?> 3445 ]]> 3446 </programlisting> 3447 3448 </para> 3449 3450 <refsect3 id="{@id 9075mapping}"> 3451 <title>Mapping SQL identifiers to XML names in accordance with ISO/IEC 9075-14:2005</title> 3452 <para> 3453 The package XML_Query2XML also implements the Final Committee Draft for ISO/IEC 9075-14:2005, 3454 section "9.1 Mapping SQL <identifier>s to XML Names". ISO/IEC 9075-14:2005 is available 3455 online at {@link http://www.sqlx.org/SQL-XML-documents/5FCD-14-XML-2004-07.pdf}. 3456 </para> 3457 <para> 3458 A lot of characters are legal in SQL identifiers but cannot be used within 3459 XML names. To begin with, SQL identifiers can contain any Unicode character 3460 while XML names are limited to a certain set of characters. E.g the 3461 SQL identifier "<21yrs in age" obviously is not a valid XML name. 3462 '#', '{', and '}' are also not allowed. Fully escaped SQL identifiers 3463 also must not contain a column (':') or start with "xml" (in any case 3464 combination). Illegal characters are mapped to a string of the form 3465 _xUUUU_ where UUUU is the Unicode value of the character. 3466 </para> 3467 <para> 3468 The following is a table of example mappings: 3469 <![CDATA[ 3470+----------------+------------------------+------------------------------------+ 3471| SQL-Identifier | Fully escaped XML name | Comment | 3472+----------------+------------------------+------------------------------------+ 3473| dept:id | dept_x003A_id | ":" is illegal | 3474| xml_name | _x0078_ml_name | must not start with [Xx][Mm][Ll] | 3475| XML_name | _x0058_ML_name | must not start with [Xx][Mm][Ll] | 3476| hire date | hire_x0020_date | space is illegal too | 3477| Works@home | Works_x0040_home | "@" is illegal | 3478| file_xls | file_x005F_xls | "_" gets mapped if followed by "x" | 3479| FIRST_NAME | FIRST_NAME | no problem here | 3480+----------------+------------------------+------------------------------------+ 3481 ]]> 3482 </para> 3483 <para> 3484 The ISO 9075-mapping does produce some overhead which might not be needed in 3485 a lot of situations. Therefore it is not the default mapper. In most cases 3486 it will be sufficient to validate your XML schema once using tools like the free 3487 {@link http://www.altova.com/download_spy_home.html XMLSpy Home Edition}. 3488 </para> 3489 <para> 3490 To use the ISO 9075-mapper that comes with XML_Query2XML you have to: 3491 <itemizedlist> 3492 <listitem> 3493 make sure that {@link http://pear.php.net/package/I18N_UnicodeString PEAR I18N_UnicodeString} 3494 is installed - see {@tutorial XML_Query2XML.pkg#requirements}. 3495 </listitem> 3496 <listitem> 3497 require XML/Query2XML/ISO9075Mapper.php 3498 </listitem> 3499 <listitem> 3500 set the option "mapper" to "XML_Query2XML_ISO9075Mapper::map" 3501 </listitem> 3502 </itemizedlist> 3503 Here goes an example: 3504 <programlisting role="php"> 3505 <![CDATA[ 3506<?php 3507require_once 'XML/Query2XML.php'; 3508require_once 'XML/Query2XML/ISO9075Mapper.php'; 3509require_once 'MDB2.php'; 3510$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 3511$dom = $query2xml->getXML( 3512 "SELECT * FROM artist", 3513 array( 3514 'rootTag' => 'favorite_artists', 3515 'idColumn' => 'artistid', 3516 'rowTag' => 'artist', 3517 'mapper' => 'XML_Query2XML_ISO9075Mapper::map', 3518 'elements' => array( 3519 '*' 3520 ) 3521 ) 3522); 3523header('Content-Type: application/xml'); 3524$dom->formatOutput = true; 3525print $dom->saveXML(); 3526?> 3527 ]]> 3528 </programlisting> 3529 </para> 3530 </refsect3> 3531 <refsect3 id="{@id own_mappers}"> 3532 <title>Building your own mappers</title> 3533 <para> 3534 There are cases when you will want the tag and attribute names to be somehow different from 3535 the column names. Let's say you want to use the column names as tag and attribute names 3536 but make them all uppercase. Certainly you could write code like this: 3537 <programlisting role="php"> 3538 <![CDATA[ 3539<?php 3540require_once 'XML/Query2XML.php'; 3541require_once 'XML/Query2XML/ISO9075Mapper.php'; 3542require_once 'MDB2.php'; 3543$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 3544$dom = $query2xml->getXML( 3545 "SELECT * FROM artist", 3546 array( 3547 'rootTag' => 'favorite_artists', 3548 'idColumn' => 'artistid', 3549 'rowTag' => 'artist', 3550 'elements' => array( 3551 'NAME' => 'name', 3552 'BIRTH_YEAR' => 'birth_year', 3553 'BIRTH_PLACE' => 'birth_place', 3554 'GENRE' => 'genre', 3555 ), 3556 'attributes' => array( 3557 'ARTISTID' => 'artistid' 3558 ) 3559 ) 3560); 3561header('Content-Type: application/xml'); 3562$dom->formatOutput = true; 3563print $dom->saveXML(); 3564?> 3565 ]]> 3566 </programlisting> 3567 But that seems a little redundant, doesn't it? In cases like these it is recommended 3568 to write your own mapper. As we want to write OO code we don't implement our mapper 3569 as a function but as a static public method of the new class UppercaseMapper. The 3570 mapper must take a string as an argument and must return a string: 3571 <programlisting role="php"> 3572 <![CDATA[ 3573<?php 3574class UppercaseMapper 3575{ 3576 public function map($str) 3577 { 3578 return strtoupper($str); 3579 } 3580} 3581 3582require_once 'XML/Query2XML.php'; 3583require_once 'MDB2.php'; 3584$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 3585$dom = $query2xml->getXML( 3586 "SELECT * FROM artist", 3587 array( 3588 'rootTag' => 'favorite_artists', 3589 'idColumn' => 'artistid', 3590 'rowTag' => 'artist', 3591 'mapper' => 'UppercaseMapper::map', 3592 'elements' => array( 3593 'name', 3594 'birth_year', 3595 'birth_place', 3596 'genre', 3597 ), 3598 'attributes' => array( 3599 'artistid' 3600 ) 3601 ) 3602); 3603header('Content-Type: application/xml'); 3604$dom->formatOutput = true; 3605print $dom->saveXML(); 3606?> 3607 ]]> 3608 </programlisting> 3609 The resulting XML data looks like this: 3610 <programlisting role="tutorial"> 3611 <![CDATA[ 3612<?xml version="1.0" encoding="UTF-8"?> 3613<favorite_artists> 3614 <artist ARTISTID="1"> 3615 <NAME>Curtis Mayfield</NAME> 3616 <BIRTH_YEAR>1920</BIRTH_YEAR> 3617 <BIRTH_PLACE>Chicago</BIRTH_PLACE> 3618 <GENRE>Soul</GENRE> 3619 </artist> 3620 <artist ARTISTID="2"> 3621 <NAME>Isaac Hayes</NAME> 3622 <BIRTH_YEAR>1942</BIRTH_YEAR> 3623 <BIRTH_PLACE>Tennessee</BIRTH_PLACE> 3624 <GENRE>Soul</GENRE> 3625 </artist> 3626 <artist ARTISTID="3"> 3627 <NAME>Ray Charles</NAME> 3628 <BIRTH_YEAR>1930</BIRTH_YEAR> 3629 <BIRTH_PLACE>Mississippi</BIRTH_PLACE> 3630 <GENRE>Country and Soul</GENRE> 3631 </artist> 3632</favorite_artists> 3633 ]]> 3634 </programlisting> 3635 </para> 3636 </refsect3> 3637 <refsect3 id="{@id multiple_mappers}"> 3638 <title>Using multiple mappers</title> 3639 <para> 3640 Let's say we want to force all tags corresponding to columns of the artist table to be uppercase 3641 and all tags corresponding to columns of the album table to be lowercase. This can be done 3642 using two mappers: 3643 <programlisting role="php"> 3644 <![CDATA[ 3645<?php 3646class MyMappers 3647{ 3648 public function uppercaseMapper($str) 3649 { 3650 return strtoupper($str); 3651 } 3652 3653 public function lowercaseMapper($str) 3654 { 3655 return strtolower($str); 3656 } 3657} 3658 3659require_once 'XML/Query2XML.php'; 3660require_once 'MDB2.php'; 3661$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 3662$dom = $query2xml->getXML( 3663 "SELECT 3664 * 3665 FROM 3666 artist", 3667 array( 3668 'rootTag' => 'music_library', 3669 'rowTag' => 'artist', 3670 'idColumn' => 'artistid', 3671 'mapper' => 'MyMappers::uppercaseMapper', 3672 'elements' => array( 3673 '*', 3674 'albums' => array( 3675 'sql' => array( 3676 'data' => array( 3677 'artistid' 3678 ), 3679 'query' => 'SELECT * FROM album WHERE artist_id = ?' 3680 ), 3681 'rootTag' => 'albums', 3682 'rowTag' => 'album', 3683 'idColumn' => 'albumid', 3684 'mapper' => 'MyMappers::lowercaseMapper', 3685 'elements' => array( 3686 '*', 3687 'artist_id' => '?:' 3688 ) 3689 ) 3690 ) 3691 ) 3692); 3693header('Content-Type: application/xml'); 3694$dom->formatOutput = true; 3695print $dom->saveXML(); 3696?> 3697 ]]> 3698 </programlisting> 3699 As we know that the columns of the album table already are lowercase we could as well 3700 use one mapper and just deactivate that for the complex element "albums': 3701 <programlisting role="php"> 3702 <![CDATA[ 3703<?php 3704class MyMappers 3705{ 3706 public function uppercaseMapper($str) 3707 { 3708 return strtoupper($str); 3709 } 3710} 3711 3712require_once 'XML/Query2XML.php'; 3713require_once 'MDB2.php'; 3714$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 3715$dom = $query2xml->getXML( 3716 "SELECT 3717 * 3718 FROM 3719 artist", 3720 array( 3721 'rootTag' => 'music_library', 3722 'rowTag' => 'artist', 3723 'idColumn' => 'artistid', 3724 'mapper' => 'MyMappers::uppercaseMapper', 3725 'elements' => array( 3726 '*', 3727 'albums' => array( 3728 'sql' => array( 3729 'data' => array( 3730 'artistid' 3731 ), 3732 'query' => 'SELECT * FROM album WHERE artist_id = ?' 3733 ), 3734 'rootTag' => 'albums', 3735 'rowTag' => 'album', 3736 'idColumn' => 'albumid', 3737 'mapper' => false, 3738 'elements' => array( 3739 '*', 3740 'artist_id' => '?:' 3741 ) 3742 ) 3743 ) 3744 ) 3745); 3746header('Content-Type: application/xml'); 3747$dom->formatOutput = true; 3748print $dom->saveXML(); 3749?> 3750 ]]> 3751 </programlisting> 3752 In both cases the resulting XML data will look like this: 3753 <programlisting role="tutorial"> 3754 <![CDATA[ 3755<?xml version="1.0" encoding="UTF-8"?> 3756<music_library> 3757 <artist> 3758 <ARTISTID>1</ARTISTID> 3759 <NAME>Curtis Mayfield</NAME> 3760 <BIRTH_YEAR>1920</BIRTH_YEAR> 3761 <BIRTH_PLACE>Chicago</BIRTH_PLACE> 3762 <GENRE>Soul</GENRE> 3763 <albums> 3764 <album> 3765 <albumid>1</albumid> 3766 <title>New World Order</title> 3767 <published_year>1990</published_year> 3768 <comment>the best ever!</comment> 3769 </album> 3770 <album> 3771 <albumid>2</albumid> 3772 <title>Curtis</title> 3773 <published_year>1970</published_year> 3774 <comment>that man's got somthin' to say</comment> 3775 </album> 3776 </albums> 3777 </artist> 3778 <artist> 3779 <ARTISTID>2</ARTISTID> 3780 <NAME>Isaac Hayes</NAME> 3781 <BIRTH_YEAR>1942</BIRTH_YEAR> 3782 <BIRTH_PLACE>Tennessee</BIRTH_PLACE> 3783 <GENRE>Soul</GENRE> 3784 <albums> 3785 <album> 3786 <albumid>3</albumid> 3787 <title>Shaft</title> 3788 <published_year>1972</published_year> 3789 <comment>he's the man</comment> 3790 </album> 3791 </albums> 3792 </artist> 3793 <artist> 3794 <ARTISTID>3</ARTISTID> 3795 <NAME>Ray Charles</NAME> 3796 <BIRTH_YEAR>1930</BIRTH_YEAR> 3797 <BIRTH_PLACE>Mississippi</BIRTH_PLACE> 3798 <GENRE>Country and Soul</GENRE> 3799 <albums /> 3800 </artist> 3801</music_library> 3802 ]]> 3803 </programlisting> 3804 </para> 3805 </refsect3> 3806 </refsect2> 3807 3808 <refsect2 id="{@id options_encoder}"> 3809 <title>$options['encoder']</title> 3810 <para> 3811 This option allows you to specifiy a function/method that performs the 3812 XML encoding for node and attribute values. Per default it is assumed 3813 that all data is in ISO-8859-1 (Latin-1) and will be encoded to UTF-8 using 3814 {@link http://at.php.net/mb_convert_encoding mb_convert_encoding()} or if not available 3815 using {@link http://at.php.net/utf8_encode utf8_encode()}. 3816 </para> 3817 <para> 3818 For some introduction to XML encoding please see 3819 {@link http://www.w3schools.com/xml/xml_encoding.asp} and 3820 {@link http://www.opentag.com/xfaq_enc.htm}. 3821 Note: I highly recommend to use UTF-8 for XML if you don't have a compelling reason 3822 to use an other encoding standard. 3823 </para> 3824 <para> 3825 The default encoding mechanism (ISO-8859-1 to UTF-8) will be just fine in most cases 3826 but sometimes your data might already be in in UTF-8 or you might not want 3827 your XML to be UTF-8 encoded at all. 3828 </para> 3829 <para> 3830 Please see {@tutorial XML_Query2XML.pkg#encoding} for how to change the encoding 3831 standard used in the XML declaration. 3832 </para> 3833 <para> 3834 $options['encoder'] can have one of the following formats: 3835 <itemizedlist> 3836 <listitem> 3837 <emphasis>'CLASS::STATIC_METHOD'</emphasis>: this syntax allows you to use a static method 3838 for encoding: 3839 <programlisting role="php"> 3840 <![CDATA[ 3841'encoder' => 'MyEncoder::encode' 3842 ]]> 3843 </programlisting> 3844 </listitem> 3845 <listitem> 3846 <emphasis>array('CLASS', 'STATIC_METHOD')</emphasis>: this syntax also allows you to use a static 3847 method for encoding: 3848 <programlisting role="php"> 3849 <![CDATA[ 3850'encoder' => array('MyEncoder', 'encode') 3851 ]]> 3852 </programlisting> 3853 </listitem> 3854 <listitem> 3855 <emphasis>array($instance, 'METHOD')</emphasis>: this syntax allows you to use a non-static 3856 method for encoding: 3857 <programlisting role="php"> 3858 <![CDATA[ 3859'encoder' => array($myEncoder, 'encode') 3860 ]]> 3861 </programlisting> 3862 </listitem> 3863 <listitem> 3864 <emphasis>'FUNCTION'</emphasis>: this syntax allows you to use a regular function 3865 for encoding: 3866 <programlisting role="php"> 3867 <![CDATA[ 3868'encoder' => 'myUTF8toISO88591Encoder' 3869 ]]> 3870 </programlisting> 3871 </listitem> 3872 <listitem> 3873 <emphasis>false</emphasis>: use the boolean value false to deactivate encoding: 3874 <programlisting role="php"> 3875 <![CDATA[ 3876'encoder' => false 3877 ]]> 3878 </programlisting> 3879 </listitem> 3880 <listitem> 3881 <emphasis>null</emphasis>: use NULL to reset encoding to the built-in default encoding. 3882 This default assumes that all data is in ISO-8859-1 (Latin-1) and will encode it to 3883 UTF-8 using {@link http://at.php.net/mb_convert_encoding mb_convert_encoding()} 3884 or if not available using {@link http://at.php.net/utf8_encode utf8_encode()}. 3885 <programlisting role="php"> 3886 <![CDATA[ 3887'encoder' => null 3888 ]]> 3889 </programlisting> 3890 </listitem> 3891 </itemizedlist> 3892 One thing you should keep in mind when writing your own encoding (wrapper) functions is 3893 that the encoder will only be called if the current record has a string value for 3894 that column; i.e. the encoder will not be called if the column value is NULL. 3895 </para> 3896 <para> 3897 The following example will show that an encoder defined at the root level is also used 3898 at all lower levels (unless it gets overwritten, see 3899 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_encoder.multiple_encoders}): 3900 <programlisting role="php"> 3901 <![CDATA[ 3902<?php 3903class SomeEncoder 3904{ 3905 public function encode($str) 3906 { 3907 //do something with $str 3908 return $str; 3909 } 3910} 3911 3912require_once 'XML/Query2XML.php'; 3913require_once 'XML/Query2XML/ISO9075Mapper.php'; 3914require_once 'MDB2.php'; 3915$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 3916$dom = $query2xml->getXML( 3917 "SELECT * FROM artist", 3918 array( 3919 'rootTag' => 'favorite_artists', 3920 'idColumn' => 'artistid', 3921 'rowTag' => 'artist', 3922 'encoder' => 'SomeEncoder::encode', /* we define an encoder at the root level */ 3923 'elements' => array( 3924 'artistid', // encoding will be 3925 'name', // performed on these 3926 'albums' => array( 3927 'sql' => array( 3928 'data' => array( 3929 'artistid' 3930 ), 3931 'query' => 'SELECT * FROM album WHERE artist_id = ?' 3932 ), 3933 'rootTag' => 'albums', 3934 'rowTag' => 'album', 3935 'idColumn' => 'albumid', 3936 'elements' => array( 3937 'albumid', // encoder setting is affective on all lower 3938 'title' // levels 3939 ), 3940 'attributes' => array( 3941 'comment' // note: encoding is also performed for attributes 3942 ) 3943 ) 3944 ) 3945 ) 3946); 3947header('Content-Type: application/xml'); 3948$dom->formatOutput = true; 3949print $dom->saveXML(); 3950?> 3951 ]]> 3952 </programlisting> 3953 </para> 3954 <refsect3 id="{@id iso88591toutf8}"> 3955 <title>ISO-8859-1 to UTF-8 encoding (default)</title> 3956 <para> 3957 This is what will automatically be performed if you do not use $options['encoder'] 3958 at all. This is because most databases use ISO-8859-1 (aka Latin-1) by default. 3959 As previously stated, XML_Query2XML will use 3960 {@link http://at.php.net/mb_convert_encoding mb_convert_encoding()} 3961 or if that is not available {@link http://at.php.net/utf8_encode utf8_encode()} for the 3962 actual encoding. 3963 </para> 3964 <para> 3965 If you have set $options['encoder'] on the root level but wish to switch back to 3966 the default on a lower level all you have to do is to use the NULL value: 3967 <programlisting role="php"> 3968 <![CDATA[ 3969'encoder' => null 3970 ]]> 3971 </programlisting> 3972 </para> 3973 </refsect3> 3974 <refsect3 id="{@id utf8toiso88591}"> 3975 <title>UTF-8 to ISO-8859-1 encoding</title> 3976 <para> 3977 If your data is in UTF-8 but you would like your XML to be in ISO-8859-1 (Latin-1), 3978 you can use {@link http://at.php.net/utf8_decode utf8_decode()}: 3979 <programlisting role="php"> 3980 <![CDATA[ 3981'encoder' => 'utf8_decode' 3982 ]]> 3983 </programlisting> 3984 or define a wrapper for {@link http://at.php.net/mb_convert_encoding mb_convert_encoding()} 3985 and use that: 3986 <programlisting role="php"> 3987 <![CDATA[ 3988function utf8ToLatin1($str) 3989{ 3990 //hint: mb_convert_encoding (str, to_encoding, from_encoding) 3991 return mb_convert_encoding($str, 'iso-8859-1', 'UTF-8'); 3992} 3993 ]]> 3994 </programlisting> 3995 specified as encoder: 3996 <programlisting role="php"> 3997 <![CDATA[ 3998'encoder' => 'utf8ToLatin1' 3999 ]]> 4000 </programlisting> 4001 </para> 4002 </refsect3> 4003 <refsect3 id="{@id disabling}"> 4004 <title>Disabling encoding</title> 4005 <para> 4006 If you data already is in the character set you wish to use for the XML, all you 4007 have to do is to disable the encoding by using a boolean value of false: 4008 <programlisting role="php"> 4009 <![CDATA[ 4010'encoder' => false 4011 ]]> 4012 </programlisting> 4013 </para> 4014 </refsect3> 4015 <refsect3 id="{@id multiple_encoders}"> 4016 <title>Using multiple encoders</title> 4017 <para> 4018 It might happen to you that some of your data sources are in one character set 4019 while others are in another. This means that you need different encoding procedures 4020 to convert them all to the same character set you wish to use for the XML (usually UTF-8). 4021 </para> 4022 <para> 4023 In the first example we will assume that all columns of the table artist are in 4024 ISO-8859-1 (Latin-1) while all columns of the table album are in UTF-8. 4025 <programlisting role="php"> 4026 <![CDATA[ 4027<?php 4028require_once 'XML/Query2XML.php'; 4029require_once 'MDB2.php'; 4030$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 4031$dom = $query2xml->getXML( 4032 "SELECT 4033 * 4034 FROM 4035 artist 4036 LEFT JOIN album ON album.artist_id = artist.artistid", 4037 array( 4038 'rootTag' => 'music_library', 4039 'rowTag' => 'artist', 4040 'idColumn' => 'artistid', 4041 'elements' => array( // all columns of the table artist are in 4042 'artistid', // ISO-8859-1; the default conversion therefore 4043 'name', // is just fine 4044 'birth_year', 4045 'birth_place', 4046 'genre', 4047 'albums' => array( 4048 'rootTag' => 'albums', 4049 'rowTag' => 'album', 4050 'idColumn' => 'albumid', 4051 'encoder' => false, // the columns of the album table already are in UTF-8; 4052 'elements' => array( // we therefore have to disable encoding 4053 'albumid', 4054 'title', 4055 'published_year', 4056 'comment' 4057 ) 4058 ) 4059 ) 4060 ) 4061); 4062 4063header('Content-Type: application/xml'); 4064 4065$dom->formatOutput = true; 4066print $dom->saveXML(); 4067?> 4068 ]]> 4069 </programlisting> 4070 4071 </para> 4072 <para> 4073 For our second example, let's assume that the following columns use the following 4074 character sets: 4075 <![CDATA[ 4076+----------------------------------------+ 4077| Column | Character Set | 4078+----------------------------------------+ 4079| artist.name | ISO-8859-1 (Latin-1) | 4080| artist.genre | UTF-8 | 4081| album.title | UTF-16 | 4082| album.comment | Windows-1252 | 4083+----------------+-----------------------+ 4084 ]]> 4085 As our XML output shall be in UTF-8 we have to use multiple encoders on a 4086 per-column basis: 4087 <programlisting role="php"> 4088 <![CDATA[ 4089<?php 4090function latin1ToUTF8($str) 4091{ 4092 return utf8_decode($str); 4093 // alternatively we could have used 4094 // return mb_convert_encoding($str, 'UTF-8', 'iso-8859-1'); 4095} 4096 4097function utf16ToUTF8($str) 4098{ 4099 return mb_convert_encoding($str, 'UTF-8', 'UTF-16'); 4100} 4101 4102function windows1252ToUTF8($str) 4103{ 4104 return mb_convert_encoding($str, 'UTF-8', 'windows-1252'); 4105} 4106 4107require_once 'XML/Query2XML.php'; 4108require_once 'MDB2.php'; 4109$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 4110$dom = $query2xml->getXML( 4111 "SELECT 4112 * 4113 FROM 4114 artist 4115 LEFT JOIN album ON album.artist_id = artist.artistid", 4116 array( 4117 'rootTag' => 'music_library', 4118 'rowTag' => 'artist', 4119 'idColumn' => 'artistid', 4120 'elements' => array( 4121 'artistid', 4122 'name', // name is in ISO-8859-1 and therefore will be handled by the default conversion 4123 'birth_year', 4124 'birth_place', 4125 'genre' => array( 4126 'value' => 'genre', 4127 'encoder' => false // genre already is in UTF-8 4128 ), 4129 'albums' => array( 4130 'rootTag' => 'albums', 4131 'rowTag' => 'album', 4132 'idColumn' => 'albumid', 4133 'elements' => array( 4134 'albumid', 4135 'title' => array( 4136 'value' => 'title', 4137 'encoder' => 'utf16ToUTF8' // title is in UTF-16 and therefore needs 4138 ), // special treatment 4139 'published_year' 4140 ), 4141 'attributes' => array( 4142 'comment' => array( 4143 'value' => 'comment', 4144 'encoder' => 'windows1252ToUTF8' // comment is in Windows-1252 4145 ) 4146 ) 4147 ) 4148 ) 4149 ) 4150); 4151 4152header('Content-Type: application/xml'); 4153 4154$dom->formatOutput = true; 4155print $dom->saveXML(); 4156?> 4157 ]]> 4158 </programlisting> 4159 4160 </para> 4161 </refsect3> 4162 </refsect2> 4163 4164 <refsect2 id="{@id return_value_modification}"> 4165 <title>Modifying the returned DOMDocument instance</title> 4166 <para> 4167 {@link XML_Query2XML::getXML()} returns an instance of 4168 {@link http://www.php.net/manual/en/ref.dom.php#dom.class.domdocument DOMDocument}. 4169 I recommend that you do some reading about 4170 {@link http://www.php.net/manual/en/ref.dom.php PHP5's DOM extension}. 4171 </para> 4172 <para> 4173 Let's see how we can add attributes to the root element 4174 (i.e. the first child of the DOMDocument instance returned by {@link XML_Query2XML::getXML() getXML()}): 4175 <programlisting role="php"> 4176 <![CDATA[ 4177<?php 4178require_once 'XML/Query2XML.php'; 4179require_once 'MDB2.php'; 4180$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 4181 4182$doc = $query2xml->getXML( 4183 "SELECT 4184 * 4185 FROM 4186 artist", 4187 array( 4188 'rootTag' => 'music_library', 4189 'rowTag' => 'artist', 4190 'idColumn' => 'artistid', 4191 'elements' => array( 4192 'artistid', 4193 'name', 4194 'birth_year', 4195 'birth_place', 4196 'genre' 4197 ) 4198 ) 4199); 4200$root = $doc->firstChild; 4201$root->setAttribute('copyright', 'John Doe 2007'); 4202 4203header('Content-Type: application/xml'); 4204$doc->formatOutput = true; 4205print $doc->saveXML(); 4206?> 4207 ]]> 4208 </programlisting> 4209 </para> 4210 <para> 4211 This adds an attribute named 'copyright' with a value of 'John Doe 2007' 4212 to the root element <music_library>: 4213 <programlisting role="tutorial"> 4214 <![CDATA[ 4215<?xml version="1.0" encoding="UTF-8"?> 4216<music_library copyright="John Doe 2007"> 4217 <artist> 4218 <artistid>1</artistid> 4219 <name>Curtis Mayfield</name> 4220 <birth_year>1920</birth_year> 4221 <birth_place>Chicago</birth_place> 4222 <genre>Soul</genre> 4223 </artist> 4224 <artist> 4225 <artistid>2</artistid> 4226 <name>Isaac Hayes</name> 4227 <birth_year>1942</birth_year> 4228 <birth_place>Tennessee</birth_place> 4229 <genre>Soul</genre> 4230 </artist> 4231 <artist> 4232 <artistid>3</artistid> 4233 <name>Ray Charles</name> 4234 <birth_year>1930</birth_year> 4235 <birth_place>Mississippi</birth_place> 4236 <genre>Country and Soul</genre> 4237 </artist> 4238</music_library> 4239 ]]> 4240 </programlisting> 4241 </para> 4242 </refsect2> 4243 4244 4245 <refsect2 id="{@id final_notes}"> 4246 <title>Final Notes on XML_Query2XML::getXML()</title> 4247 <para> 4248 You might also want to read the API docs: {@link XML_Query2XML}. 4249 </para> 4250 </refsect2> 4251 </refsect1> 4252 4253 <refsect1 id="{@id other_xml_data_sources}"> 4254 <title>Integrating other XML data sources</title> 4255 <refsect2 id="{@id intro}"> 4256 <title>Introduction</title> 4257 <para> 4258 Since release 1.1.0 it is possible to integrate other XML data sources into 4259 the XML data that is returned by {@link XML_Query2XML::getXML()}. For example 4260 you might want to store XML data in your relational database and integrate that 4261 into your xml feed. In this case the XML data is only present in serialized form 4262 and therefore needs to be unserialized first. This means that the data has to be 4263 converted into a {@link http://www.php.net/manual/en/ref.dom.php DOMDocument} using 4264 {@link http://www.php.net/manual/en/function.dom-domdocument-loadxml.php DOMDocument::loadXML()}. 4265 It might as well be that you already have a PHP application running that creates a DOMDocument. 4266 In that case no unserialization is needed. 4267 </para> 4268 </refsect2> 4269 <refsect2 id="{@id unserialization_prefix}"> 4270 <title>The unserialization prefix (&)</title> 4271 <para> 4272 To unserialize xml data you can use the UNSERIALIZATION prefix &. It has the following 4273 characteristics: 4274 <itemizedlist> 4275 <listitem> 4276 {@link http://www.php.net/manual/en/function.dom-domdocument-loadxml.php DOMDocument::loadXML()} 4277 is used for unserialization. 4278 </listitem> 4279 <listitem> 4280 If the data to unserialize is an empty string ('') or null the data will be silently 4281 ignored and no XML elements will be created. 4282 </listitem> 4283 <listitem> 4284 A XML_Query2XML_XMLException will be thrown if the unserialization fails, i.e. 4285 {@link http://www.php.net/manual/en/function.dom-domdocument-loadxml.php DOMDocument::loadXML()} 4286 returns false. This will happen for example if the data you try to unserialize is 'John Doe' (no xml tags at all), 4287 '<name>John Doe' (no closing tag) or '<name>John Doe</name><name>Jane Doe</name>' (no root tag). 4288 </listitem> 4289 </itemizedlist> 4290 </para> 4291 <refsect3 id="{@id usage_scenarios}"> 4292 <title>Usage scenarios of the unserialization prefix (&)</title> 4293 <para> 4294 Regarding a container (the root element of your unserialized data) 4295 there are 3 different things you might want when unserializing 4296 the data: 4297 <itemizedlist> 4298 <listitem> 4299 Container always present 4300 </listitem> 4301 <listitem> 4302 Container only present if there are children 4303 </listitem> 4304 <listitem> 4305 No Container 4306 </listitem> 4307 </itemizedlist> 4308 For the detailed description of each of the three possibilities that is to follow, we will assume 4309 that your XML data is stored in the database. Therefore & will be used in the form 4310 of '&COLUMN_NAME'. If you wanted to unserialize static data you would write something 4311 like 4312 <programlisting role="php"> 4313 <![CDATA[ 4314'&:<name>John Doe</name>' 4315 ]]> 4316 </programlisting> 4317 or if you wanted to unserialize a string returned from a callback function, you would use 4318 <programlisting role="php"> 4319 <![CDATA[ 4320'&#MyClass::myFunction()' 4321 ]]> 4322 </programlisting> 4323 <itemizedlist> 4324 <listitem> 4325 <emphasis>Container always present</emphasis>: The container element will be present 4326 even if there is no data to be unserialized. This is the default behaviour: 4327 <programlisting role="php"> 4328 <![CDATA[ 4329<?php 4330require_once 'XML/Query2XML.php'; 4331require_once 'MDB2.php'; 4332$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 4333$dom =& $query2xml->getXML( 4334 "SELECT 4335 *, NULL AS additional_xml 4336 FROM 4337 store", 4338 array( 4339 'rootTag' => 'music_stores', 4340 'rowTag' => 'store', 4341 'idColumn' => 'storeid', 4342 'elements' => array( 4343 'storeid', 4344 'country', 4345 'state', 4346 'city', 4347 'street', 4348 'building_xmldata' => '&building_xmldata', 4349 'additional_xml' => '&additional_xml', 4350 ) 4351 ) 4352); 4353$dom->formatOutput = true; 4354print $dom->saveXML(); 4355?> 4356 ]]> 4357 </programlisting> 4358 For both records the 'building_xmldata' column contains a <building> element 4359 that has 3 children: <floors>, <elevators> and <square_meters>. But 4360 there is always a surrounding <building_xmldata> tag. The 'additional_xml' column 4361 is NULL for both records but an empty <additional_xml/> element gets created for 4362 both of them. 4363 <programlisting role="tutorial"> 4364 <![CDATA[ 4365<?xml version="1.0" encoding="UTF-8"?> 4366<music_stores> 4367 <store> 4368 <storeid>1</storeid> 4369 <country>US</country> 4370 <state>New York</state> 4371 <city>New York</city> 4372 <street>Broadway & 72nd Str</street> 4373 <building_xmldata> 4374 <building> 4375 <floors>4</floors> 4376 <elevators>2</elevators> 4377 <square_meters>3200</square_meters> 4378 </building> 4379 </building_xmldata> 4380 <additional_xml/> 4381 </store> 4382 <store> 4383 <storeid>2</storeid> 4384 <country>US</country> 4385 <state>New York</state> 4386 <city>Larchmont</city> 4387 <street>Palmer Ave 71</street> 4388 <building_xmldata> 4389 <building> 4390 <floors>2</floors> 4391 <elevators>1</elevators> 4392 <square_meters>400</square_meters> 4393 </building> 4394 </building_xmldata> 4395 <additional_xml/> 4396 </store> 4397</music_stores> 4398 ]]> 4399 </programlisting> 4400 Note: you would get exactly the same result by using the 'value' option within 4401 a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.complex_element_specifications} 4402 instead of a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications}. 4403 Instead of 4404 <programlisting role="php"> 4405 <![CDATA[ 4406'building_xmldata' => '&building_xmldata' 4407 ]]> 4408 </programlisting> 4409 you would write 4410 <programlisting role="php"> 4411 <![CDATA[ 4412'building_xmldata' => array( 4413 'value' => '&building_xmldata' 4414) 4415 ]]> 4416 </programlisting> 4417 </listitem> 4418 <listitem> 4419 <emphasis>Container only present if there are children</emphasis>: The container will only be 4420 present if the unserialization produces at least one XML element. This is achieved 4421 by using the CONDITIONAL prefix (?): 4422 <programlisting role="php"> 4423 <![CDATA[ 4424<?php 4425require_once 'XML/Query2XML.php'; 4426require_once 'MDB2.php'; 4427$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 4428$dom =& $query2xml->getXML( 4429 "SELECT 4430 *, NULL AS additional_xml 4431 FROM 4432 store", 4433 array( 4434 'rootTag' => 'music_stores', 4435 'rowTag' => 'store', 4436 'idColumn' => 'storeid', 4437 'elements' => array( 4438 'storeid', 4439 'country', 4440 'state', 4441 'city', 4442 'street', 4443 'building_xmldata' => '?&building_xmldata', 4444 'additional_xml' => '?&additional_xml', 4445 ) 4446 ) 4447); 4448$dom->formatOutput = true; 4449print $dom->saveXML(); 4450?> 4451 ]]> 4452 </programlisting> 4453 The resulting XML data shows that the unserialized XML is still enclosed 4454 by a <building_xmldata> tag but the <additional_xml> elements are gone: 4455 <programlisting role="tutorial"> 4456 <![CDATA[ 4457<?xml version="1.0" encoding="UTF-8"?> 4458<music_stores> 4459 <store> 4460 <storeid>1</storeid> 4461 <country>US</country> 4462 <state>New York</state> 4463 <city>New York</city> 4464 <street>Broadway & 72nd Str</street> 4465 <building_xmldata> 4466 <building> 4467 <floors>4</floors> 4468 <elevators>2</elevators> 4469 <square_meters>3200</square_meters> 4470 </building> 4471 </building_xmldata> 4472 </store> 4473 <store> 4474 <storeid>2</storeid> 4475 <country>US</country> 4476 <state>New York</state> 4477 <city>Larchmont</city> 4478 <street>Palmer Ave 71</street> 4479 <building_xmldata> 4480 <building> 4481 <floors>2</floors> 4482 <elevators>1</elevators> 4483 <square_meters>400</square_meters> 4484 </building> 4485 </building_xmldata> 4486 </store> 4487</music_stores> 4488 ]]> 4489 </programlisting> 4490 Again: the same results can be achieved by using the 'value' option within 4491 a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.complex_element_specifications} 4492 instead of a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications}. 4493 Instead of 4494 <programlisting role="php"> 4495 <![CDATA[ 4496'building_xmldata' => '?&building_xmldata' 4497 ]]> 4498 </programlisting> 4499 you would write 4500 <programlisting role="php"> 4501 <![CDATA[ 4502'building_xmldata' => array( 4503 'value' => '?&building_xmldata' 4504) 4505 ]]> 4506 </programlisting> 4507 </listitem> 4508 <listitem> 4509 <emphasis>No Container</emphasis>: Even if the unserialization produces an XML element 4510 no container will be used. You have to effectively hide the container by using the 4511 hidden_container_prefix that can be set using 4512 {@tutorial XML_Query2XML.pkg#global_options.setglobaloption} and defaults to '__'. Here 4513 goes an example: 4514 <programlisting role="php"> 4515 <![CDATA[ 4516<?php 4517require_once 'XML/Query2XML.php'; 4518require_once 'MDB2.php'; 4519$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 4520$dom =& $query2xml->getXML( 4521 "SELECT 4522 * 4523 FROM 4524 store", 4525 array( 4526 'rootTag' => 'music_stores', 4527 'rowTag' => 'store', 4528 'idColumn' => 'storeid', 4529 'elements' => array( 4530 'storeid', 4531 'country', 4532 'state', 4533 'city', 4534 'street', 4535 '__building_xmldata' => '&building_xmldata' 4536 ) 4537 ) 4538); 4539$dom->formatOutput = true; 4540print $dom->saveXML(); 4541?> 4542 ]]> 4543 </programlisting> 4544 The resulting XML now does not contain <building_xmldata> tags that surround 4545 the <building> elements: 4546 <programlisting role="tutorial"> 4547 <![CDATA[ 4548<?xml version="1.0" encoding="UTF-8"?> 4549<music_stores> 4550 <store> 4551 <storeid>1</storeid> 4552 <country>US</country> 4553 <state>New York</state> 4554 <city>New York</city> 4555 <street>Broadway & 72nd Str</street> 4556 <building> 4557 <floors>4</floors> 4558 <elevators>2</elevators> 4559 <square_meters>3200</square_meters> 4560 </building> 4561 </store> 4562 <store> 4563 <storeid>2</storeid> 4564 <country>US</country> 4565 <state>New York</state> 4566 <city>Larchmont</city> 4567 <street>Palmer Ave 71</street> 4568 <building> 4569 <floors>2</floors> 4570 <elevators>1</elevators> 4571 <square_meters>400</square_meters> 4572 </building> 4573 </store> 4574</music_stores> 4575 ]]> 4576 </programlisting> 4577 The same results can be achieved by using the 'value' option within 4578 a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.complex_element_specifications} 4579 instead of a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications}. 4580 Instead of 4581 <programlisting role="php"> 4582 <![CDATA[ 4583'__building_xmldata' => '&building_xmldata' 4584 ]]> 4585 </programlisting> 4586 you would write 4587 <programlisting role="php"> 4588 <![CDATA[ 4589'__building_xmldata' => array( 4590 'value' => '&building_xmldata' 4591) 4592 ]]> 4593 </programlisting> 4594 or (which is effectively the same) 4595 <programlisting role="php"> 4596 <![CDATA[ 4597'building_xmldata' => array( 4598 'rowTag' => '__building_xmldata' 4599 'value' => '&building_xmldata' 4600) 4601 ]]> 4602 </programlisting> 4603 Please also note that using the CONDITIONAL prefix (?) in conjunction with 4604 the hidden_container_prefix '__' does not change the resulting XML data in any way: 4605 <programlisting role="php"> 4606 <![CDATA[ 4607'__building_xmldata' => '?&building_xmldata' 4608 ]]> 4609 </programlisting> 4610 </listitem> 4611 </itemizedlist> 4612 </para> 4613 </refsect3> 4614 <refsect3 id="{@id writing_your_own}"> 4615 <title>Writing your own unserialization method</title> 4616 <para> 4617 If you are unhappy with these chracteristics (e.g. you want invalid XML data to be ignored 4618 rather than causing an exception to be thrown) you could do your own unserialization using 4619 a CALLBACK FUNCTION (i.e. the # prefix). Here is what the unserialization performed by 4620 the & prefix looks like 4621 <programlisting role="php"> 4622 <![CDATA[ 4623if (strlen($xmlData)) { 4624 $doc = new DOMDocument(); 4625 if (!@$doc->loadXML($xmlData)) { 4626 throw new XML_Query2XML_XMLException( 4627 'Could not unserialize the following XML data: ' 4628 . $ret 4629 ); 4630 } 4631 return $doc->documentElement; 4632} else { 4633 return null; 4634} 4635 ]]> 4636 </programlisting> 4637 </para> 4638 </refsect3> 4639 </refsect2> 4640 <refsect2 id="{@id callbacks}"> 4641 <title>Returning DOMNode instances from callbacks</title> 4642 <para> 4643 If you want to integrate XML_Query2XML with another PHP application that uses 4644 PHP5's {@link http://www.php.net/manual/en/ref.dom.php DOM}, 4645 you can make your callbacks return an instance of 4646 {@link http://www.php.net/manual/en/ref.dom.php DOMNode} or an array of DOMNode instances. 4647 </para> 4648 <para> 4649 In the following example we return a <unixtime> tag from the callback function 4650 getTime(). It will be placed inside a <time> tag. 4651 <programlisting role="php"> 4652 <![CDATA[ 4653<?php 4654require_once 'XML/Query2XML.php'; 4655require_once 'MDB2.php'; 4656$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 4657$dom =& $query2xml->getXML( 4658 "SELECT 4659 * 4660 FROM 4661 album", 4662 array( 4663 'rootTag' => 'music_store', 4664 'rowTag' => 'album', 4665 'idColumn' => 'albumid', 4666 'elements' => array( 4667 'albumid', 4668 'title', 4669 'time' => '#getTime()' 4670 ) 4671 ) 4672); 4673$dom->formatOutput = true; 4674print $dom->saveXML(); 4675 4676function getTime() 4677{ 4678 $dom = new DOMDocument(); 4679 $unixtime = $dom->createElement('unixtime'); 4680 $unixtime->appendChild($dom->createTextNode(time())); 4681 return $unixtime; 4682} 4683?> 4684 ]]> 4685 </programlisting> 4686 Have a look at the resulting XML data: 4687 <programlisting role="tutorial"> 4688 <![CDATA[ 4689<?xml version="1.0" encoding="UTF-8"?> 4690<music_store> 4691 <album> 4692 <albumid>1</albumid> 4693 <title>New World Order</title> 4694 <time> 4695 <unixtime>1167167461</unixtime> 4696 </time> 4697 </album> 4698 <album> 4699 <albumid>2</albumid> 4700 <title>Curtis</title> 4701 <time> 4702 <unixtime>1167167461</unixtime> 4703 </time> 4704 </album> 4705 <album> 4706 <albumid>3</albumid> 4707 <title>Shaft</title> 4708 <time> 4709 <unixtime>1167167461</unixtime> 4710 </time> 4711 </album> 4712</music_store> 4713 ]]> 4714 </programlisting> 4715 Now we modify the example so that getTime() returns multiple DOMNode instances in an array. 4716 We will also "hide" the surrounding element using the hidden_container_prefix that can be 4717 set using {@tutorial XML_Query2XML.pkg#global_options.setglobaloption} and defaults to '__': 4718 <programlisting role="php"> 4719 <![CDATA[ 4720<?php 4721require_once 'XML/Query2XML.php'; 4722require_once 'MDB2.php'; 4723$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 4724$dom =& $query2xml->getXML( 4725 "SELECT 4726 * 4727 FROM 4728 album", 4729 array( 4730 'rootTag' => 'music_store', 4731 'rowTag' => 'album', 4732 'idColumn' => 'albumid', 4733 'elements' => array( 4734 'albumid', 4735 'title', 4736 '__time' => '#getTime()' 4737 ) 4738 ) 4739); 4740$dom->formatOutput = true; 4741print $dom->saveXML(); 4742 4743function getTime() 4744{ 4745 $dom = new DOMDocument(); 4746 $unixtime = $dom->createElement('unixtime'); 4747 $unixtime->appendChild($dom->createTextNode(time())); 4748 4749 $rfc2822date = $dom->createElement('rfc2822date'); 4750 $rfc2822date->appendChild($dom->createTextNode(date('r'))); 4751 return array($unixtime, $rfc2822date); 4752} 4753?> 4754 ]]> 4755 </programlisting> 4756 The surrounding <time> element is now gone and we have both tags <unixtime> 4757 and <rfc2822date>: 4758 <programlisting role="tutorial"> 4759 <![CDATA[ 4760<?xml version="1.0" encoding="UTF-8"?> 4761<music_store> 4762 <album> 4763 <albumid>1</albumid> 4764 <title>New World Order</title> 4765 <unixtime>1167169325</unixtime> 4766 <rfc2822date>Tue, 26 Dec 2006 22:42:05 +0100</rfc2822date> 4767 </album> 4768 <album> 4769 <albumid>2</albumid> 4770 <title>Curtis</title> 4771 <unixtime>1167169325</unixtime> 4772 <rfc2822date>Tue, 26 Dec 2006 22:42:05 +0100</rfc2822date> 4773 </album> 4774 <album> 4775 <albumid>3</albumid> 4776 <title>Shaft</title> 4777 <unixtime>1167169325</unixtime> 4778 <rfc2822date>Tue, 26 Dec 2006 22:42:05 +0100</rfc2822date> 4779 </album> 4780</music_store> 4781 ]]> 4782 </programlisting> 4783 </para> 4784 </refsect2> 4785 </refsect1> 4786 4787 <refsect1 id="{@id exception_handling}"> 4788 <title>Exception Handling</title> 4789 <para> 4790 The public methods {@link XML_Query2XML::factory()}, {@link XML_Query2XML::getFlatXML()} 4791 and {@link XML_Query2XML::getXML()} all may throw exceptions. For production use you will 4792 have to implement the security principle "secure failure". This means that you will have to 4793 catch exceptions and deal with them. XML_Query2XML makes this task easy as all exceptions this 4794 package will ever throw extend {@link XML_Query2XML_Exception}. Therefore it is possible to catch all 4795 exceptions by catching {@link XML_Query2XML_Exception}: 4796 </para> 4797 <para> 4798 <programlisting role="php"> 4799 <![CDATA[ 4800<?php 4801require_once 'XML/Query2XML.php'; 4802try { 4803 require_once 'MDB2.php'; 4804 $query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 4805 $dom = $query2xml->getXML( 4806 "SELECT * FROM artist", 4807 array( 4808 'rootTag' => 'favorite_artist', 4809 'idColumn' => 'artistid', 4810 'rowTag' => 'artist', 4811 'elements' => array( 4812 'name', 4813 'birth_year', 4814 'genre' 4815 ) 4816 ) 4817 ); 4818 echo $dom->saveXML(); 4819} catch(XML_Query2XML_Exception $e) { 4820 /* 4821 * log this exceptions 4822 * display some error message that does not disclose sensitive information 4823 */ 4824} 4825?> 4826 ]]> 4827 </programlisting> 4828 </para> 4829 <para> 4830 Here is a list of the exceptions the public methods of XML_Query2XML will throw: 4831 <itemizedlist> 4832 <listitem> 4833 {@link XML_Query2XML::factory()}: {@link XML_Query2XML_DBException} and {@link XML_Query2XML_ConfigException} 4834 </listitem> 4835 <listitem> 4836 {@link XML_Query2XML::getFlatXML()}: {@link XML_Query2XML_DBException} and 4837 {@link XML_Query2XML_XMLException} 4838 </listitem> 4839 <listitem> 4840 {@link XML_Query2XML::getXML()}: {@link XML_Query2XML_DBException}, 4841 {@link XML_Query2XML_XMLException} and {@link XML_Query2XML_ConfigException} 4842 </listitem> 4843 </itemizedlist> 4844 As you can see, XML_Query2XML_Exception itself is never thrown. 4845 </para> 4846 <para> 4847 To treat different exceptions differently you would write code like this: 4848<programlisting role="php"> 4849 <![CDATA[ 4850<?php 4851require_once 'XML/Query2XML.php'; 4852try { 4853 require_once 'MDB2.php'; 4854 $query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 4855 $dom = $query2xml->getXML( 4856 "SELECT * FROM artist", 4857 array( 4858 'rootTag' => 'favorite_artist', 4859 'idColumn' => 'artistid', 4860 'rowTag' => 'artist', 4861 'elements' => array( 4862 'name', 4863 'birth_year', 4864 'genre' 4865 ) 4866 ) 4867 ); 4868 echo $dom->saveXML(); 4869} catch(XML_Query2XML_DBException $e) { 4870 //handle DB error 4871} catch(XML_Query2XML_XMLException $e) { 4872 //handle XML error 4873} catch(XML_Query2XML_Exception $e) { 4874 /* 4875 * Handle all other errors/exceptions; this will not only catch 4876 * XML_Query2XML_ConfigException but also all other exceptions that might be 4877 * added in future releases of XML_Query2XML. 4878 */ 4879} 4880?> 4881 ]]> 4882 </programlisting> 4883 Bottom line: make sure you at least have a catch block for XML_Query2XML_Exception. 4884 </para> 4885 </refsect1> 4886 4887 <refsect1 id="{@id formatting}"> 4888 <title>Output formatting</title> 4889 <para> 4890 Before calling the saveXML() method on your DOMDocument instance set its public property 4891 {@link http://at.php.net/manual/en/ref.dom.php#dom.class.domdocument.properties formatOutput} to true! 4892 Here goes an example: 4893 <programlisting role="php"> 4894 <![CDATA[ 4895<?php 4896require_once 'XML/Query2XML.php'; 4897require_once 'MDB2.php'; 4898$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 4899$dom = $query2xml->getXML( 4900 "SELECT * FROM artist", 4901 array( 4902 'rootTag' => 'favorite_artist', 4903 'idColumn' => 'artistid', 4904 'rowTag' => 'artist', 4905 'elements' => array( 4906 'name', 4907 'birth_year', 4908 'genre' 4909 ) 4910 ) 4911); 4912header('Content-Type: application/xml'); 4913 4914$dom->formatOutput = true; 4915print $dom->saveXML(); 4916?> 4917 ]]> 4918 </programlisting> 4919 </para> 4920 <para> 4921 Alternatively you could also use {@link http://pear.php.net/package/XML_Beautifier PEAR XML_Beautifier}. 4922 Here goes an example: 4923 <programlisting role="php"> 4924 <![CDATA[ 4925<?php 4926require_once 'XML/Query2XML.php'; 4927require_once 'MDB2.php'; 4928$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 4929$dom = $query2xml->getXML( 4930 "SELECT * FROM artist", 4931 array( 4932 'rootTag' => 'favorite_artist', 4933 'idColumn' => 'artistid', 4934 'rowTag' => 'artist', 4935 'elements' => array( 4936 'name', 4937 'birth_year', 4938 'genre' 4939 ) 4940 ) 4941); 4942header('Content-Type: application/xml'); 4943print '<?xml version="1.0" encoding="UTF-8"?>' . "\n"; 4944 4945require_once 'XML/Beautifier.php'; 4946$beautifier = new XML_Beautifier(); 4947print $beautifier->formatString($dom->saveXML()); 4948?> 4949 ]]> 4950 </programlisting> 4951 </para> 4952 </refsect1> 4953 4954 <refsect1 id="{@id output_caching}"> 4955 <title>Output Caching</title> 4956 <para> 4957 If your XML data is rather static in nature, i.e. exactely the same XML 4958 data is created over and over again you might want to use some kind of output caching. 4959 I will demonstrate the usage of {@link http://pear.php.net/package/Cache_Lite PEAR Cache_Lite} here: 4960 </para> 4961 <para> 4962 <programlisting role="php"> 4963 <![CDATA[ 4964<?php 4965require_once('Cache/Lite.php'); 4966/* 4967* Set a id for this cache; if you generate the XML data based 4968* on values passed via POST/GET/COOKIE, include these values 4969* in $id. This does not need to be an integer; it's md5sum 4970* will be used. 4971*/ 4972$id = '123'; 4973 4974//set a few options 4975$options = array( 4976 'cacheDir' => "/tmp/", 4977 'lifeTime' => 3600 4978); 4979 4980//create a Cache_Lite object 4981$cache = new Cache_Lite($options); 4982 4983// content type for xml data 4984header('Content-Type: application/xml'); 4985 4986//test if there is a valide cache for this id 4987if ($data = $cache->get($id)) { 4988 print $data; 4989} else { 4990 //no valid cache found 4991 require_once 'XML/Query2XML.php'; 4992 require_once 'MDB2.php'; 4993 $query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 4994 $dom = $query2xml->getXML(...); 4995 $dom->formatOutput = true; 4996 4997 //cache the XML data 4998 $data = $dom->saveXML(); 4999 $cache->save($data, $id); 5000 5001 print $data; 5002} 5003?> 5004 ]]> 5005 </programlisting> 5006 For more information on PEAR Cache_Lite, please see the 5007 {@link http://pear.php.net/manual/en/package.caching.cache-lite.php Cache_Lite manual}. 5008 </para> 5009 </refsect1> 5010 5011 <refsect1 id="{@id encoding}"> 5012 <title>XML encoding</title> 5013 <para> 5014 It is highly recommended to use UTF-8 for your XML data. But if you want 5015 to use another encoding standard you have to: 5016 <itemizedlist> 5017 <listitem> 5018 Use {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_encoder} to convert 5019 all node and attribute values to the desired encoding standard. If source and 5020 destination encoding standards are the same, you just have to set 5021 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_encoder} to false. 5022 </listitem> 5023 <listitem> 5024 Set the encoding property of the DOMDocument instance returned by XML_Query2XML::getXML(). 5025 </listitem> 5026 </itemizedlist> 5027 </para> 5028 <para> 5029 For some introduction to XML encoding please see 5030 {@link http://www.w3schools.com/xml/xml_encoding.asp} and 5031 {@link http://www.opentag.com/xfaq_enc.htm}. 5032 Note: I highly recommend to use UTF-8 for XML if you don't have a compelling reason 5033 to use an other encoding standard. 5034 </para> 5035 <para> 5036 Here goes an example that shows how to use ISO-8859-1 (Latin-1) for XML encoding. We 5037 will assume that your data is in ISO-8859-1 and therefore does not need any conversion. 5038 <programlisting role="php"> 5039 <![CDATA[ 5040<?php 5041require_once 'XML/Query2XML.php'; 5042require_once 'MDB2.php'; 5043$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 5044$dom = $query2xml->getXML( 5045 "SELECT 5046 * 5047 FROM 5048 artist", 5049 array( 5050 'rootTag' => 'music_library', 5051 'rowTag' => 'artist', 5052 'idColumn' => 'artistid', 5053 'encoder' => false, // disable default ISO-8859-1 to UTF-8 encoding 5054 'elements' => array( 5055 'artistid', 5056 'name', 5057 'birth_year', 5058 'birth_place', 5059 'genre' 5060 ) 5061 ) 5062); 5063 5064header('Content-Type: application/xml'); 5065$dom->formatOutput = true; 5066 5067$dom->encoding = 'iso-8859-1'; //setting XML encoding 5068 5069print $dom->saveXML(); 5070?> 5071 ]]> 5072 </programlisting> 5073 This results in the following XML: 5074 <programlisting role="tutorial"> 5075 <![CDATA[ 5076<?xml version="1.0" encoding="iso-8859-1"?> 5077<music_library> 5078 <artist> 5079 <artistid>1</artistid> 5080 <name>Curtis Mayfield</name> 5081 <birth_year>1920</birth_year> 5082 <birth_place>Chicago</birth_place> 5083 <genre>Soul</genre> 5084 </artist> 5085 <artist> 5086 <artistid>2</artistid> 5087 <name>Isaac Hayes</name> 5088 <birth_year>1942</birth_year> 5089 <birth_place>Tennessee</birth_place> 5090 <genre>Soul</genre> 5091 </artist> 5092 <artist> 5093 <artistid>3</artistid> 5094 <name>Ray Charles</name> 5095 <birth_year>1930</birth_year> 5096 <birth_place>Mississippi</birth_place> 5097 <genre>Country and Soul</genre> 5098 </artist> 5099</music_library> 5100 ]]> 5101 </programlisting> 5102 </para> 5103 </refsect1> 5104 5105 5106 <refsect1 id="{@id binary_data}"> 5107 <title>Handling Binary Data</title> 5108 <para> 5109 If you want to include binary data (e.g. JPEG data) in XML data you should encode your binary data 5110 to make sure that it does not include a sequence of bytes that represent the characters "</" 5111 or a null byte (which usually denotes the end of a string). The most common binary data 5112 encoding for XML is base64. The most straightforward way to do base64 encoding for 5113 an element or attribute value is to use the BASE64 ENCODING shortcut '^'. 5114 </para> 5115 <para> 5116 In the following example we will assume that the column album.comment contains binary data: 5117 <programlisting role="php"> 5118 <![CDATA[ 5119<?php 5120require_once 'XML/Query2XML.php'; 5121require_once 'MDB2.php'; 5122 5123$mdb2 = MDB2::factory('mysql://root@localhost/Query2XML_Tests'); 5124$query2xml = XML_Query2XML::factory($mdb2); 5125 5126$dom = $query2xml->getXML( 5127 'SELECT * FROM album', 5128 array( 5129 'idColumn' => 'albumid', 5130 'rowTag' => 'album', 5131 'rootTag' => 'music_store', 5132 'elements' => array( 5133 'albumid', 5134 'title', 5135 'comment' => '^comment' 5136 ) 5137 ) 5138); 5139 5140header('Content-Type: application/xml'); 5141$dom->formatOutput = true; 5142print $dom->saveXML(); 5143?> 5144 ]]> 5145 </programlisting> 5146 The resulting XML data looks like this: 5147 <programlisting role="tutorial"> 5148 <![CDATA[ 5149<?xml version="1.0" encoding="UTF-8"?> 5150<music_store> 5151 <album> 5152 <albumid>1</albumid> 5153 <title>New World Order</title> 5154 <comment>dGhlIGJlc3QgZXZlciE=</comment> 5155 </album> 5156 <album> 5157 <albumid>2</albumid> 5158 <title>Curtis</title> 5159 <comment>dGhhdCBtYW4ncyBnb3Qgc29tdGhpbicgdG8gc2F5</comment> 5160 </album> 5161 <album> 5162 <albumid>3</albumid> 5163 <title>Shaft</title> 5164 <comment>aGUncyB0aGUgbWFu</comment> 5165 </album> 5166</music_store> 5167 ]]> 5168 </programlisting> 5169 </para> 5170 </refsect1> 5171 5172 <refsect1 id="{@id dumpdatabase}"> 5173 <title>Using dynamic $options to dump all data of your database</title> 5174 <para> 5175 For some reason you might simply want to dump every table in your database 5176 with all their records. If you don't want to go over your code everytime a 5177 new table was added, you need to generate (parts of) the $options argument on 5178 the fly. Here is one way to do it: 5179 <programlisting role="php"> 5180 <![CDATA[ 5181<?php 5182require_once 'XML/Query2XML.php'; 5183require_once 'MDB2.php'; 5184 5185$mdb2 = MDB2::factory('mysql://root@localhost/Query2XML_Tests'); 5186$query2xml = XML_Query2XML::factory($mdb2); 5187 5188 5189//we need MDB2's manager module to get the list of tables in a database independent way 5190$mdb2->loadModule('Manager'); 5191$elements = array(); 5192$mdb2->setOption('portability', MDB2_PORTABILITY_NONE); 5193$tables = $mdb2->listTables(); 5194$mdb2->setOption('portability', MDB2_PORTABILITY_ALL); 5195for ($i = 0; $i < count($tables); $i++) { 5196 $elements['table' . $i] = array( 5197 'rowTag' => 'table', 5198 'attributes' => array( 5199 'name' => ':' . $tables[$i] 5200 ), 5201 'elements' => array( 5202 'record' => array( 5203 'idColumn' => false, 5204 'sql' => 'SELECT * FROM ' . $tables[$i], 5205 'elements' => array( 5206 '*' 5207 ) 5208 ) 5209 ) 5210 ); 5211} 5212 5213$dom = $query2xml->getXML( 5214 false, 5215 array( 5216 'idColumn' => false, 5217 'rowTag' => '__tables', 5218 'rootTag' => 'database', 5219 'elements' => $elements 5220 ) 5221); 5222 5223header('Content-Type: application/xml'); 5224$dom->formatOutput = true; 5225print $dom->saveXML(); 5226?> 5227 ]]> 5228 </programlisting> 5229 </para> 5230 <para> 5231 Notice how we used MDB2's manager module to get a list of all tables in our database. 5232 We then loop over the names and create 5233 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements} 5234 at run time. 5235 </para> 5236 <para> 5237 Side note: unfortunately MDB2's portability feature per default makes listTabes() return all table names lower-cased. 5238 To circumvent this we have to temporarily change the portability option. See {@link http://pear.php.net/bugs/bug.php?id=11215} 5239 which describes the issue in greater detail. 5240 </para> 5241 <para> 5242 Each table tag will have an attribute "name". We use the ':' prefix to indicate that 5243 what follows is a static text not to be interpreted as a column name. 5244 </para> 5245 <para> 5246 When looking at the getXML() call, you'll notice that we didn't pass a query as the 5247 first argument ({@tutorial XML_Query2XML.pkg#query2xml_getxml.sql}) but rather a boolean 5248 value of false. As documented at {@tutorial XML_Query2XML.pkg#query2xml_getxml.sql} 5249 this will make XML_Query2XML behave as if we used a query that returned a single record 5250 with no columns. This is necessary because in our example we use multiple unrelated queries 5251 that we simply want to palce inside a database tag. 5252 </para> 5253 <para> 5254 Also notice that we use the boolean value of FALSE for 5255 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_idcolumn} 5256 at the root level and for each of the 5257 tables. It is OK to do so on the root level because we are actually only dealing with a 5258 single fake record there. To use FALSE for {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_idcolumn} 5259 on the sub-levels within the $elements array 5260 generated at run-time is also OK because we will always want all records and (which is 5261 the reason why we are not violating best practices) we simply don't know the primary key 5262 columns for all tables that might get created in the future. 5263 </para> 5264 <para> 5265 Also note that we used '__tables' for {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_rowtag} 5266 at the root level: this is because we don't have anything to loop over at the root level - remember 5267 using false for {@tutorial XML_Query2XML.pkg#query2xml_getxml.sql} is like using a 5268 query that returns a single record with no columns. 5269 </para> 5270 </refsect1> 5271 5272 <refsect1 id="{@id without_prefixes}"> 5273 <title>Working without Shortcuts and Prefixes</title> 5274 <para> 5275 You think having to deal with all these different prefixes (e.g. #, ?, :, &, ^ and =) when defining 5276 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_value} and 5277 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications} 5278 just makes things more complicated or your code less readable? In that case, just don't use them! Below is 5279 way of getting almost the same functionality with a command object pattern implementation. If you have 5280 to deal with some 10,000+ records in your XML, using command objects might even improve performance 5281 a little bit (~1-3%). 5282 </para> 5283 <para> 5284 The following example first provides command classes that implement the functionality 5285 of the currently available prefixes. 5286 <programlisting role="php"> 5287 <![CDATA[ 5288<?php 5289/**All command objects have to implement the interface XML_Query2XML_Callback. 5290*/ 5291require_once 'XML/Query2XML/Callback.php'; 5292 5293/**All command classes that work on a single column value will extend this abstract class. 5294* Classes extending this class will have to implement the execute(array $record) method 5295* defined by the XML_Query2XML_Callback interface. 5296*/ 5297abstract class Callback_SingleColumn implements XML_Query2XML_Callback 5298{ 5299 /**The column name 5300 * @var string 5301 */ 5302 protected $_column = ''; 5303 5304 /**Constructor 5305 * @param string $column The name of the column this instance will work on. 5306 */ 5307 public function __construct($column) 5308 { 5309 $this->_column = $column; 5310 } 5311 5312 /**Get the value of the column passed to the constructor. 5313 * @param array $record An associative array as it will be passed 5314 * to the execute(array $record) method. 5315 * @throws XML_Query2XML_Exception If the column name passed 5316 * to the constructor was not found in $record. 5317 */ 5318 protected function _getColumnValue(array $record) 5319 { 5320 if (array_key_exists($this->_column, $record)) { 5321 return $record[$this->_column]; 5322 } 5323 throw new XML_Query2XML_Exception( 5324 'Column ' . $this->_column . ' was not found in the result set' 5325 ); 5326 } 5327} 5328 5329/**Use an instance of this class to get the base64 encoded value of a column. 5330*/ 5331class Callback_Base64 extends Callback_SingleColumn 5332{ 5333 /**Called by XML_Query2XML for every record. 5334 * @param array $record An associative array. 5335 * @return string 5336 */ 5337 public function execute(array $record) 5338 { 5339 return base64_encode($this->_getColumnValue($record)); 5340 } 5341} 5342 5343/**Use an instance of this class to unserialize XML data stored in a column. 5344*/ 5345class Callback_Unserialization extends Callback_SingleColumn 5346{ 5347 /**Called by XML_Query2XML for every record. 5348 * @param array $record An associative array. 5349 * @return DOMElement 5350 * @throws XML_Query2XML_XMLException If unserialization fails. 5351 */ 5352 public function execute(array $record) 5353 { 5354 $doc = new DOMDocument(); 5355 $xml = $this->_getColumnValue($record); 5356 if (!@$doc->loadXML($xml)) { 5357 throw new XML_Query2XML_XMLException( 5358 'Could not unserialize the following XML data: ' 5359 . $xml 5360 ); 5361 } 5362 return $doc->documentElement; 5363 } 5364} 5365 5366/**Use an instance of this class to place a CDATA section around the value of a column. 5367*/ 5368class Callback_CDATA extends Callback_SingleColumn 5369{ 5370 /**Called by XML_Query2XML for every record. 5371 * @param array $record An associative array. 5372 * @return DOMCDATASection 5373 */ 5374 public function execute(array $record) 5375 { 5376 $doc = new DOMDocument(); 5377 return $doc->createCDATASection($this->_getColumnValue($record)); 5378 } 5379} 5380 5381/**Use an instance of this class to return a static data. 5382*/ 5383class Callback_StaticData implements XML_Query2XML_Callback 5384{ 5385 /**The static data 5386 * @var mixed 5387 */ 5388 private $_data = null; 5389 5390 /**Constructor 5391 * @param mixed $data The static date to return for every record. 5392 */ 5393 public function __construct($data) 5394 { 5395 $this->_data = $data; 5396 } 5397 5398 /**Called by XML_Query2XML for every record. 5399 * This method will always return the same data, no matter what 5400 * is passed as $record. 5401 * 5402 * @param array $record An associative array. 5403 * @return mixed 5404 */ 5405 public function execute(array $record) 5406 { 5407 return $this->_data; 5408 } 5409} 5410 5411require_once 'XML/Query2XML.php'; 5412require_once 'MDB2.php'; 5413 5414$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 5415 5416$xml = $query2xml->getXML( 5417 'SELECT * FROM store', 5418 array( 5419 'rootTag' => 'stores', 5420 'rowTag' => 'store', 5421 'idColumn' => 'storeid', 5422 'elements' => array( 5423 'base64' => new Callback_Base64('building_xmldata'), 5424 'cdata' => new Callback_CDATA('building_xmldata'), 5425 'static' => new Callback_StaticData('my static data'), 5426 'unserialized' => new Callback_Unserialization('building_xmldata') 5427 ) 5428 ) 5429); 5430 5431$xml->formatOutput = true; 5432print $xml->saveXML(); 5433?> 5434 ]]> 5435 </programlisting> 5436 Note that the above code is equivalent to 5437 <programlisting role="php"> 5438 <![CDATA[ 5439<?php 5440require_once 'XML/Query2XML.php'; 5441require_once 'MDB2.php'; 5442 5443$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 5444 5445$xml = $query2xml->getXML( 5446 'SELECT * FROM store', 5447 array( 5448 'rootTag' => 'stores', 5449 'rowTag' => 'store', 5450 'idColumn' => 'storeid', 5451 'elements' => array( 5452 'base64' => '?^building_xmldata', 5453 'cdata' => '=building_xmldata', 5454 'static' => ':my static data', 5455 'unserialized' => '&building_xmldata' 5456 ) 5457 ) 5458); 5459 5460$xml->formatOutput = true; 5461print $xml->saveXML(); 5462?> 5463 ]]> 5464 </programlisting> 5465 Both produce the following XML data: 5466 <programlisting role="tutorial"> 5467 <![CDATA[ 5468<?xml version="1.0" encoding="UTF-8"?> 5469<stores> 5470 <store> 5471 <base64>PGJ1aWxkaW5nPjxmbG9vcnM+NDwvZmxvb3JzPjxlbGV2YXRvcnM+MjwvZWxldmF0b3JzPjxzcXVhcmVfbWV0ZXJzPjMyMDA8L3NxdWFyZV9tZXRlcnM+PC9idWlsZGluZz4=</base64> 5472 <cdata>< ![CDATA[<building><floors>4</floors><elevators>2</elevators><square_meters>3200</square_meters></building>]] ></cdata> 5473 <static>my static data</static> 5474 <unserialized> 5475 <building> 5476 <floors>4</floors> 5477 <elevators>2</elevators> 5478 <square_meters>3200</square_meters> 5479 </building> 5480 </unserialized> 5481 </store> 5482 <store> 5483 <base64>PGJ1aWxkaW5nPjxmbG9vcnM+MjwvZmxvb3JzPjxlbGV2YXRvcnM+MTwvZWxldmF0b3JzPjxzcXVhcmVfbWV0ZXJzPjQwMDwvc3F1YXJlX21ldGVycz48L2J1aWxkaW5nPg==</base64> 5484 <cdata>< ![CDATA[<building><floors>2</floors><elevators>1</elevators><square_meters>400</square_meters></building>]] ></cdata> 5485 <static>my static data</static> 5486 <unserialized> 5487 <building> 5488 <floors>2</floors> 5489 <elevators>1</elevators> 5490 <square_meters>400</square_meters> 5491 </building> 5492 </unserialized> 5493 </store> 5494</stores> 5495 ]]> 5496 </programlisting> 5497 </para> 5498 <para> 5499 Finally, note that you may also explecitely unregister a specific prefix using {@link XML_Query2XML::unregisterPrefix()} 5500 are unregister all previously defined prefixes using {@link XML_Query2XML::unregisterAllPrefixes()}. 5501 </para> 5502 </refsect1> 5503 5504 <refsect1 id="{@id your_own_prefixes}"> 5505 <title>Defining your own Prefixes</title> 5506 <para> 5507 You may want to define your own prefixes in order to have a simple and quick way to invoke complex functionality you need often. 5508 The registration of the prefix is very much straight forward: 5509 </para> 5510 <para> 5511 {@link XML_Query2XML::registerPrefix() XML_Query2XML::registerPrefix}($prefix, $className, $filePath = '') 5512 </para> 5513 <para> 5514 Note that you may also register a prefix that has been registered before (or is registered by default). 5515 In that case, the new registration will simply overwrite the old one. 5516 </para> 5517 <para> 5518 More difficult than simply registering the prefix, is writing the class that will implement its functionality. 5519 All such classes will have to implement the interface {@link XML_Query2XML_Data}. However, in practice you will 5520 want to extend one of the following abstract classes, depending on the type of functionality you need: 5521 <itemizedlist> 5522 <listitem> 5523 <emphasis>{@link XML_Query2XML_Data_Processor}</emphasis>: extend this class if you want to use any data 5524 as input other than the string specified right after the prefix. For example, if you want to use a column 5525 value as input data, extend this class and let the built-in code handle the rest. (Note: the 5526 BASE64 ENCODING prefix ^, CDATA SECTION prefix =, and the XML UNSERIALIZATION prefix & are built using 5527 this class.) 5528 </listitem> 5529 <listitem> 5530 <emphasis>{@link XML_Query2XML_Data_Source}</emphasis>: extend this class if you only want to use the string 5531 specified right after the prefix as your data source. (Note: the STATIC TEXT prefix : and the CALLBACK FUNCTION 5532 prefix # are built using this class.) 5533 </listitem> 5534 <listitem> 5535 <emphasis>{@link XML_Query2XML_Data_Condition}</emphasis>: extend this class if you want to implement some 5536 kind of condition that will determine whether the XML element for which the value was to be used will be 5537 included in the resulting output. (Note: the CONDITIONAL prefix ? is built using this class.) 5538 </listitem> 5539 </itemizedlist> 5540 </para> 5541 <para> 5542 Now, let's suppose you want to use the prefix "§" for your command class Year2UnixTime which calculates 5543 the Unix time (seconds since the Unix Epoch, January 1 1970 00:00:00 GMT) from a given year. In such a 5544 case you clearly want to extend {@link XML_Query2XML_Data_Processor}: 5545 <programlisting role="php"> 5546 <![CDATA[ 5547<?php 5548require_once 'XML/Query2XML.php'; 5549require_once 'XML/Query2XML/Data/Processor.php'; 5550require_once 'MDB2.php'; 5551 5552class Year2UnixTime extends XML_Query2XML_Data_Processor 5553{ 5554 /** 5555 * Create a new instance of this class. 5556 * 5557 * @param mixed $preProcessor The pre-processor to be used. An instance of 5558 * XML_Query2XML_Data or null. 5559 * @param string $configPath The configuration path within the $options 5560 * array. 5561 * 5562 * @return XML_Query2XML_Data_Processor_Base64 5563 */ 5564 public function create($preProcessor, $configPath) 5565 { 5566 $processor = new Year2UnixTime($preProcessor); 5567 // The create() method of every class extending 5568 // XML_Query2XML_Data_Processor has to call 5569 // setConfigPath() manually! 5570 $processor->setConfigPath($configPath); 5571 return $processor; 5572 } 5573 5574 /** 5575 * Called by XML_Query2XML for every record in the result set. 5576 * 5577 * @param array $record An associative array. 5578 * 5579 * @return string The base64-encoded version the string returned 5580 * by the pre-processor. 5581 * @throws XML_Query2XML_ConfigException If the pre-processor returns 5582 * something that cannot be converted to a string 5583 * (i.e. an object or an array). 5584 */ 5585 public function execute(array $record) 5586 { 5587 $data = $this->runPreProcessor($record); 5588 if (is_array($data) || is_object($data)) { 5589 throw new XML_Query2XML_ConfigException( 5590 $this->getConfigPath() 5591 . ': XML_Query2XML_Data_Processor_Base64: string ' 5592 . 'expected from pre-processor, but ' . gettype($data) . ' returned.' 5593 ); 5594 } 5595 return DateTime::createFromFormat('Y-m-d H:i:s', $data . '-01-01 00:00:00', new DateTimeZone('Etc/GMT+0'))->format('U'); 5596 } 5597} 5598 5599$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 5600$query2xml->registerPrefix('§', 'Year2UnixTime'); 5601$dom = $query2xml->getXML( 5602 "SELECT * FROM artist", 5603 array( 5604 'rootTag' => 'artists', 5605 'idColumn' => 'artistid', 5606 'rowTag' => 'artist', 5607 'elements' => array( 5608 'name', 5609 'birth_year', 5610 'birth_year_in_unix_time' => '§birth_year' 5611 ) 5612 ) 5613); 5614 5615header('Content-Type: application/xml'); 5616$dom->formatOutput = true; 5617print $dom->saveXML(); 5618?> 5619 ]]> 5620 </programlisting> 5621 The resulting XML data looks like this: 5622 <programlisting role="tutorial"> 5623 <![CDATA[ 5624<?xml version="1.0" encoding="UTF-8"?> 5625<artists> 5626 <artist> 5627 <name>Curtis Mayfield</name> 5628 <birth_year>1920</birth_year> 5629 <birth_year_in_unix_time>-1577923200</birth_year_in_unix_time> 5630 </artist> 5631 <artist> 5632 <name>Isaac Hayes</name> 5633 <birth_year>1942</birth_year> 5634 <birth_year_in_unix_time>-883612800</birth_year_in_unix_time> 5635 </artist> 5636 <artist> 5637 <name>Ray Charles</name> 5638 <birth_year>1930</birth_year> 5639 <birth_year_in_unix_time>-1262304000</birth_year_in_unix_time> 5640 </artist> 5641</artists> 5642 ]]> 5643 </programlisting> 5644 </para> 5645 </refsect1> 5646 5647 <refsect1 id="{@id global_options}"> 5648 <title>Global Options</title> 5649 <para> 5650 Global options can be set using the public method 5651 {@tutorial XML_Query2XML.pkg#global_options.setglobaloption} and retrieved using 5652 {@tutorial XML_Query2XML.pkg#global_options.getglobaloption}. Currently the 5653 following global options are available: 5654 <itemizedlist> 5655 <listitem> 5656 {@tutorial XML_Query2XML.pkg#global_options.hidden_container_prefix} 5657 </listitem> 5658 </itemizedlist> 5659 </para> 5660 <refsect2 id="{@id setglobaloption}"> 5661 <title>XML_Query2XML::setGlobalOption()</title> 5662 <para> 5663 {@link XML_Query2XML::setGlobalOption() XML_Query2XML::setGlobalOption}($option, $value) 5664 </para> 5665 <para> 5666 Currently there is only one global option: {@tutorial XML_Query2XML.pkg#global_options.hidden_container_prefix} 5667 which has to be set to a non empty string. If you try to set a non existing global option or 5668 try to set an existing one to an invalid value a {@link XML_Query2XML_ConfigException} 5669 will be thrown. 5670 </para> 5671 <para> 5672 Here goes an example: 5673 <programlisting role="php"> 5674 <![CDATA[ 5675$query2xml =& XML_Query2XML::factory($db); 5676$query2xml->setGlobalOption('hidden_container_prefix', '___'); 5677 ]]> 5678 </programlisting> 5679 </para> 5680 </refsect2> 5681 <refsect2 id="{@id getglobaloption}"> 5682 <title>XML_Query2XML::getGlobalOption()</title> 5683 <para> 5684 {@link XML_Query2XML::getGlobalOption() XML_Query2XML::getGlobalOption}($option) 5685 </para> 5686 <para> 5687 Currently there is only one global option: {@tutorial XML_Query2XML.pkg#global_options.hidden_container_prefix}. 5688 Use getGlobalOption() to retrieve a global option's current value: 5689 <programlisting role="php"> 5690 <![CDATA[ 5691$query2xml =& XML_Query2XML::factory($db); 5692echo $query2xml->getGlobalOption('hidden_container_prefix'); 5693 ]]> 5694 </programlisting> 5695 If you try to retrieve the value of a non existing global option a 5696 {@link XML_Query2XML_ConfigException} will be thrown. 5697 </para> 5698 </refsect2> 5699 <refsect2 id="{@id hidden_container_prefix}"> 5700 <title>hidden_container_prefix</title> 5701 <para> 5702 All elements whose name start with the string specified in the 5703 hidden_container_prefix option are stripped from the DOMDocument that is finally 5704 returned by {@tutorial XML_Query2XML.pkg#query2xml_getxml}. The container's child 5705 elements are effectively replacing their parent. The default value of the 5706 hidden_container_prefix option is '__'. 5707 Here is an example using the default: 5708 <programlisting role="php"> 5709 <![CDATA[ 5710<?php 5711require_once 'XML/Query2XML.php'; 5712require_once 'MDB2.php'; 5713$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 5714$dom =& $query2xml->getXML( 5715 "SELECT 5716 * 5717 FROM 5718 album", 5719 array( 5720 'rootTag' => 'music_store', 5721 'rowTag' => 'album', 5722 'idColumn' => 'albumid', 5723 'elements' => array( 5724 'albumid', 5725 'title', 5726 '__info' => '&:<name>John Doe</name>' 5727 ) 5728 ) 5729); 5730$dom->formatOutput = true; 5731print $dom->saveXML(); 5732?> 5733 ]]> 5734 </programlisting> 5735 Instead of 5736 <programlisting role="php"> 5737 <![CDATA[ 5738'__info' => '&:<name>John Doe</name>' 5739 ]]> 5740 </programlisting> 5741 we also could have written 5742 <programlisting role="php"> 5743 <![CDATA[ 5744'name' => ':John Doe' 5745 ]]> 5746 </programlisting> 5747 Both versions produce the same XML data. Note how the contents of the <__info> 5748 element effectively replaced the <__info> tag itself. It results in the <name> element 5749 being directly placed inside the <album> element. 5750 <programlisting role="tutorial"> 5751 <![CDATA[ 5752<?xml version="1.0" encoding="UTF-8"?> 5753<music_store> 5754 <album> 5755 <albumid>1</albumid> 5756 <title>New World Order</title> 5757 <name>John Doe</name> 5758 </album> 5759 <album> 5760 <albumid>2</albumid> 5761 <title>Curtis</title> 5762 <name>John Doe</name> 5763 </album> 5764 <album> 5765 <albumid>3</albumid> 5766 <title>Shaft</title> 5767 <name>John Doe</name> 5768 </album> 5769</music_store> 5770 ]]> 5771 </programlisting> 5772 </para> 5773 <para> 5774 Now imagine that we actually do want an XML element to be named '__info'. This 5775 means that we have to change the hidden_container_prefix option: 5776 <programlisting role="php"> 5777 <![CDATA[ 5778<?php 5779require_once 'XML/Query2XML.php'; 5780require_once 'MDB2.php'; 5781$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 5782$query2xml->setGlobalOption('hidden_container_prefix', '___'); 5783$dom =& $query2xml->getXML( 5784 "SELECT 5785 * 5786 FROM 5787 album", 5788 array( 5789 'rootTag' => 'music_store', 5790 'rowTag' => 'album', 5791 'idColumn' => 'albumid', 5792 'elements' => array( 5793 'albumid', 5794 'title', 5795 '__info' => '&:<name>John Doe</name>', //will not be hidden 5796 '___info' => '&:<name>John Doe</name>' //will be hidden 5797 ) 5798 ) 5799); 5800$dom->formatOutput = true; 5801print $dom->saveXML(); 5802?> 5803 ]]> 5804 </programlisting> 5805 This produces the following XML data: 5806 <programlisting role="tutorial"> 5807 <![CDATA[ 5808<?xml version="1.0" encoding="UTF-8"?> 5809<music_store> 5810 <album> 5811 <albumid>1</albumid> 5812 <title>New World Order</title> 5813 <__info> 5814 <name>John Doe</name> 5815 </__info> 5816 <name>John Doe</name> 5817 </album> 5818 <album> 5819 <albumid>2</albumid> 5820 <title>Curtis</title> 5821 <__info> 5822 <name>John Doe</name> 5823 </__info> 5824 <name>John Doe</name> 5825 </album> 5826 <album> 5827 <albumid>3</albumid> 5828 <title>Shaft</title> 5829 <__info> 5830 <name>John Doe</name> 5831 </__info> 5832 <name>John Doe</name> 5833 </album> 5834</music_store> 5835 ]]> 5836 </programlisting> 5837 </para> 5838 </refsect2> 5839 </refsect1> 5840 <refsect1 id="{@id debugging}"> 5841 <title>Logging and Debugging XML_Query2XML</title> 5842 <para> 5843 If you need to debug your XML_Query2XML or an application using it, use {@link XML_Query2XML::enableDebugLog()} 5844 and {@link XML_Query2XML::disableDebugLog()}. It is recommended to use 5845 {@link http://pear.php.net/package/Log PEAR Log}. 5846 </para> 5847 <para> 5848 The following information is logged: 5849 <itemizedlist> 5850 <listitem> 5851 the beginning of the execution of a SQL query in the database: 5852 this includes the SQL statement itself followed by the values used 5853 when executing a prepared statement. 5854 </listitem> 5855 <listitem> 5856 the end of the execution of a SQL query in the database 5857 </listitem> 5858 <listitem> 5859 caching of a query's result 5860 </listitem> 5861 <listitem> 5862 retrieving previously cached data 5863 </listitem> 5864 </itemizedlist> 5865 When using PEAR::Log, the date, time, a custom string and '[info]' will preceed every 5866 entry. 5867 </para> 5868 <para> 5869 Here is how it's done: 5870 <programlisting role="php"> 5871 <![CDATA[ 5872<?php 5873require_once 'XML/Query2XML.php'; 5874$query2xml = XML_Query2XML::factory($db); 5875 5876//create a new instance of PEAR::Log 5877require_once 'Log.php'; 5878$debugLogger = Log::factory('file', 'debug.log', 'XML_Query2XML'); 5879 5880//start debugging 5881$query2xml->enableDebugLog($debugLogger); 5882 5883$dom = $query2xml->getXML( 5884 "SELECT 5885 * 5886 FROM 5887 artist 5888 ORDER BY 5889 artistid", 5890 array( 5891 'rootTag' => 'music_library', 5892 'rowTag' => 'artist', 5893 'idColumn' => 'artistid', 5894 'elements' => array( 5895 'name', 5896 'albums' => array( 5897 'sql' => array( 5898 'data' => array( 5899 'artistid' 5900 ), 5901 'query' => 'SELECT * FROM album WHERE artist_id = ?' 5902 ), 5903 'rowTag' => 'album', 5904 'idColumn' => 'albumid', 5905 'elements' => array( 5906 'title' 5907 ) 5908 ) 5909 ) 5910 ) 5911); 5912print $dom->saveXML(); 5913?> 5914 ]]> 5915 </programlisting> 5916 This will write the following to debug.log: 5917 <![CDATA[ 5918Apr 18 20:54:30 XML_Query2XML [info] QUERY: SELECT 5919 * 5920 FROM 5921 artist 5922 ORDER BY 5923 artistid 5924Apr 18 20:54:30 XML_Query2XML [info] DONE 5925Apr 18 20:54:30 XML_Query2XML [info] QUERY: SELECT * FROM album WHERE artist_id = ?; DATA:1 5926Apr 18 20:54:30 XML_Query2XML [info] DONE 5927Apr 18 20:54:30 XML_Query2XML [info] QUERY: SELECT * FROM album WHERE artist_id = ?; DATA:2 5928Apr 18 20:54:30 XML_Query2XML [info] DONE 5929Apr 18 20:54:30 XML_Query2XML [info] QUERY: SELECT * FROM album WHERE artist_id = ?; DATA:3 5930Apr 18 20:54:30 XML_Query2XML [info] DONE 5931 ]]> 5932 XML_Query2XML simply logs the SQL SELECT string the way it was passed to getXML(), including all 5933 whitespace characters. If the query in question is a complex join over multiple tables this 5934 might be a good thing. On the other hand you might just want to have a single line per log entry. 5935 This is best achieved by building a wrapper around PEAR Log: 5936 <programlisting role="php"> 5937 <![CDATA[ 5938class MyLogger { 5939 private $_logger = null; 5940 public function __construct($logger) 5941 { 5942 $this->_logger = $logger; 5943 } 5944 5945 public function log($msg) 5946 { 5947 $this->_logger->log(preg_replace('/\n\ +/m', ' ', $msg)); 5948 } 5949} 5950 ]]> 5951 </programlisting> 5952 Using this wrapper like this: 5953 <programlisting role="php"> 5954 <![CDATA[ 5955$query2xml->enableDebugLog( 5956 new MyLogger( 5957 Log::factory('file', 'debug.log', 'XML_Query2XML') 5958 ) 5959); 5960 ]]> 5961 </programlisting> 5962 will make the log file from the prvious example look like this: 5963 <![CDATA[ 5964Apr 18 20:55:51 XML_Query2XML [info] QUERY: SELECT * FROM artist ORDER BY artistid 5965Apr 18 20:55:51 XML_Query2XML [info] DONE 5966Apr 18 20:55:51 XML_Query2XML [info] QUERY: SELECT * FROM album WHERE artist_id = ?; DATA:1 5967Apr 18 20:55:51 XML_Query2XML [info] DONE 5968Apr 18 20:55:51 XML_Query2XML [info] QUERY: SELECT * FROM album WHERE artist_id = ?; DATA:2 5969Apr 18 20:55:51 XML_Query2XML [info] DONE 5970Apr 18 20:55:51 XML_Query2XML [info] QUERY: SELECT * FROM album WHERE artist_id = ?; DATA:3 5971Apr 18 20:55:51 XML_Query2XML [info] DONE 5972 ]]> 5973 Note how the first query is now placed on a single line. 5974 </para> 5975 5976 <refsect2 id="{@id enabledebuglog}"> 5977 <title>XML_Query2XML::enableDebugLog()</title> 5978 <para> 5979 Please see the API docs at {@link XML_Query2XML::enableDebugLog()}. 5980 </para> 5981 </refsect2> 5982 5983 <refsect2 id="{@id disabledebuglog}"> 5984 <title>XML_Query2XML::disableDebugLog()</title> 5985 <para> 5986 Please see the API docs at {@link XML_Query2XML::disableDebugLog()}. 5987 </para> 5988 </refsect2> 5989 5990 </refsect1> 5991 5992 <refsect1 id="{@id profiling}"> 5993 <title>Profiling and Performance Tuning</title> 5994 <para> 5995 When the amount of data you have to deal with is getting bigger and bigger you will start to ask yourself 5996 questions like: 5997 <itemizedlist> 5998 <listitem> 5999 Are two smaller joins faster than a single huge one? 6000 </listitem> 6001 <listitem> 6002 Should I use {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql} with or without caching? 6003 </listitem> 6004 <listitem> 6005 How often does a certain query get executed and how long does it take? 6006 </listitem> 6007 </itemizedlist> 6008 XML_Query2XML's profiling provides help on giving answers to these questions. 6009 </para> 6010 6011 <refsect2 id="{@id example}"> 6012 <title>Example</title> 6013 <para> 6014 {@tutorial XML_Query2XML.pkg#profiling.startprofiling} should be the first and 6015 {@tutorial XML_Query2XML.pkg#profiling.getprofile} the last method you call on your XML_Query2XML instance. 6016 <programlisting role="php"> 6017 <![CDATA[ 6018<?php 6019require_once 'XML/Query2XML.php'; 6020$query2xml = XML_Query2XML::factory($db); 6021 6022//start the profiling as soon as possible 6023$query2xml->startProfiling(); 6024 6025//do the real work 6026$dom = $query2xml->getXML(...); 6027print $dom->saveXML(); 6028 6029//save the profile in a separate file 6030require_once 'File.php'; 6031$fp = new File(); 6032$fp->write('/tmp/query2xml_profile.txt', $query2xml->getProfile(), FILE_MODE_WRITE); 6033?> 6034 ]]> 6035 </programlisting> 6036 </para> 6037 </refsect2> 6038 6039 <refsect2 id="{@id startprofiling}"> 6040 <title>XML_Query2XML::startProfiling()</title> 6041 <para> 6042 {@link XML_Query2XML::startProfiling()} will start the profiling by 6043 initializing the private variable {@link XML_Query2XML::$_profile}. See 6044 {@tutorial XML_Query2XML.pkg#profiling.getrawprofile} for details on its data format. 6045 </para> 6046 </refsect2> 6047 6048 <refsect2 id="{@id stopprofiling}"> 6049 <title>XML_Query2XML::stopProfiling()</title> 6050 <para> 6051 {@link XML_Query2XML::stopProfiling()} will stop the profiling. In most cases you will not 6052 need to call this method as {@tutorial XML_Query2XML.pkg#profiling.getprofile} will do so implicitly. 6053 </para> 6054 </refsect2> 6055 6056 <refsect2 id="{@id getprofile}"> 6057 <title>XML_Query2XML::getProfile()</title> 6058 <para> 6059 {@link XML_Query2XML::getProfile()} will return the profile as a multiline string. 6060 It is a table with the following columns: 6061 <itemizedlist> 6062 <listitem> 6063 FROM_DB: number of times this type of query executed in the database 6064 </listitem> 6065 <listitem> 6066 FROM_CACHE: number of times the results could be retrieved from cache 6067 </listitem> 6068 <listitem> 6069 CACHED: whether caching was performed (true or false); if caching was performed but FROM_CACHE 6070 is 0, the value will be "true!" to indicate that no caching is necessary 6071 </listitem> 6072 <listitem> 6073 AVG_DURATION: average duration of executing the query and getting it's results 6074 </listitem> 6075 <listitem> 6076 DURATION_SUM: total duration for all queries of this type 6077 </listitem> 6078 <listitem> 6079 SQL: the query itself 6080 </listitem> 6081 </itemizedlist> 6082 Additionally there will be a summary at the end of the file. It will contain two fields: 6083 <itemizedlist> 6084 <listitem> 6085 TOTAL_DURATION: number of seconds the whole operation took (including outputting everything). 6086 Whether you output the generated XML data will only affect TOTAL_DURATION but not DB_DURATION. 6087 </listitem> 6088 <listitem> 6089 DB_DURATION: number of seconds spent executing SQL queries and retrieving their results. 6090 </listitem> 6091 </itemizedlist> 6092 </para> 6093 </refsect2> 6094 6095 <refsect2 id="{@id getrawprofile}"> 6096 <title>XML_Query2XML::getRawProfile()</title> 6097 <para> 6098 {@link XML_Query2XML::getRawProfile()} will return the raw profile data as a multi dimensional associative array. 6099 It has the following format: 6100 6101 <programlisting role="php"> 6102 <![CDATA[ 6103$this->_profile = array( 6104 'queries' => array(), 6105 'start' => microtime(1), 6106 'stop' => 0, 6107 'duration' => 0, 6108 'dbStop' => 0, 6109 'dbDuration' => 0 6110); 6111 ]]> 6112 </programlisting> 6113 The element 'queries' is itself an associative array that 6114 uses $sql as the array key;: 6115 <programlisting role="php"> 6116 <![CDATA[ 6117$this->_profile['queries'][$sql] = array( 6118 'fromDB' => 0, 6119 'fromCache' => 0, 6120 'cached' => false, 6121 'runTimes' => array() 6122); 6123 ]]> 6124 </programlisting> 6125 The element 'runTimes' is an indexed array that stores multiple 6126 arrays that have the following format: 6127 <programlisting role="php"> 6128 <![CDATA[ 6129array('start' => microtime(true), 'stop' => 0); 6130 ]]> 6131 </programlisting> 6132 </para> 6133 </refsect2> 6134 6135 <refsect2 id="{@id clearprofile}"> 6136 <title>XML_Query2XML::clearProfile()</title> 6137 <para> 6138 Please see the API docs at {@link XML_Query2XML::clearProfile()}. 6139 </para> 6140 </refsect2> 6141 </refsect1> 6142 6143 <refsect1 id="{@id ldap}"> 6144 <title>The LDAP Driver</title> 6145 <para> 6146 Since v1.6.0RC1 XML_Query2XML comes with a driver for {@link http://pear.php.net/package/Net_LDAP PEAR Net_LDAP}. 6147 The driver for {@link http://pear.php.net/package/Net_LDAP2 PEAR Net_LDAP2} is available since v1.7.0RC1. 6148 This allows you to use LDAP instead of an RDBMS as your primary data source. To use the PEAR Net_LDAP 6149 driver you can pass an instance of Net_LDAP to {@tutorial XML_Query2XML.pkg#query2xml_factory}. The only 6150 thing that changes is the format of {@tutorial XML_Query2XML.pkg#query2xml_getxml.sql} and 6151 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql}. 6152 </para> 6153 <refsect2 id="{@id queryspecification}"> 6154 <title>LDAP Query Specification</title> 6155 <para> 6156 All LDAP queries have to be specified as an array. 6157 LDAP query specifications can be used 6158 for {@tutorial XML_Query2XML.pkg#query2xml_getxml.sql} and 6159 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql}. They look as follows: 6160 <programlisting role="php"> 6161 <![CDATA[ 6162array( 6163 'data' => array(...), // optional; only relevant if placeholders are used in 'base' or 'filter' 6164 'base' => 'ou=peopole,dc=example,dc=com', 6165 'filter' => '(objectclass=inetOrgPerson)', 6166 'options' => array( 6167 'attributes' => array( 6168 ... 6169 ), ... 6170 ) 6171) 6172 ]]> 6173 </programlisting> 6174 These are the arguments as they will be passed to 6175 {@link http://pear.php.net/package/Net_LDAP/docs/latest/Net_LDAP/Net_LDAP.html#methodsearch Net_LDAP::search()}. 6176 Please see the {@link http://pear.php.net/manual/en/package.networking.net-ldap.search.php Net_LDAP manual} 6177 for details. 6178 </para> 6179 <para> 6180 Note that $ldapQuery['options']['attributes'] will be used by the LDAP driver if it is present. It allows the driver 6181 to set the specified attributes to null if they are not present in the returned records. 6182 It is therefore highly recommended to always set the option 'attributes'. 6183 See {@tutorial XML_Query2XML.pkg#ldap.optionalattributes} for details. 6184 <programlisting role="php"> 6185 <![CDATA[ 6186array( 6187 'base' => 'ou=peopole,dc=example,dc=com', 6188 'filter' => '(objectclass=inetOrgPerson)', 6189 'options' => array( 6190 'attributes' => array( 6191 'cn', 6192 'mobile' 6193 ) 6194 ) 6195) 6196 ]]> 6197 </programlisting> 6198 </para> 6199 <para> 6200 Now let's look at an example. We want all entries from ou=people,dc=example,dc=com that 6201 have an objectclass of inetOrgPerson. For each of these entries we want to output 6202 the attributes cn and mobile. 6203 <programlisting role="php"> 6204 <![CDATA[ 6205<?php 6206require_once 'XML/Query2XML.php'; 6207require_once 'Net/LDAP.php'; 6208 6209/* 6210* just pass an instance of Net_LDAP to the factory method 6211* as you would do it with an MDB2 or PDO instance. 6212*/ 6213$query2xml = XML_Query2XML::factory(Net_LDAP::connect()); 6214 6215$dom = $query2xml->getXML( 6216 array( //this is different than what you're used to from the other drivers 6217 'base' => 'ou=people,dc=example,dc=com', 6218 'filter' => '(objectclass=inetOrgPerson)', 6219 'options' => array( 6220 'attributes' => array( //to tell the LDAP driver which columns to watch out for 6221 'cn', 6222 'mobile', 6223 ) 6224 ) 6225 ), 6226 array( 6227 'rootTag' => 'employees', 6228 'idColumn' => 'cn', 6229 'rowTag' => 'employee', 6230 'elements' => array( 6231 'cn', 6232 'mobile' 6233 ) 6234 ) 6235); 6236header('Content-Type: application/xml'); 6237 6238$dom->formatOutput = true; 6239print $dom->saveXML(); 6240?> 6241 ]]> 6242 </programlisting> 6243 This would produce the following XML data: 6244 <programlisting role="tutorial"> 6245 <![CDATA[ 6246<?xml version="1.0" encoding="UTF-8"?> 6247<employees> 6248 <employee> 6249 <cn>John Doe</cn> 6250 <mobile>666-777-888</mobile> 6251 </employee> 6252 <employee> 6253 <cn>Jane Doe</cn> 6254 <mobile>555-777-888</mobile> 6255 </employee> 6256</employees> 6257 ]]> 6258 </programlisting> 6259 </para> 6260 <para> 6261 An LDAP Query Specification that makes use of placeholders and the option 'data' looks like this: 6262 <programlisting role="php"> 6263 <![CDATA[ 6264array( 6265 'data' => array(':John Doe'), 6266 'base' => 'ou=peopole,dc=example,dc=com', 6267 'filter' => '(&(objectclass=inetOrgPerson)(cn=?))', 6268 'options' => array( 6269 'attributes' => array( 6270 'cn', 6271 'mobile' 6272 ), 6273 'query2xml_placeholder' => '?' 6274 ) 6275) 6276 ]]> 6277 </programlisting> 6278 An LDAP query specification provides a prepare&execute-like funktionality: a placeholder 6279 (by default a question mark) can be used in ['base'] and ['filter']. 6280 That placeholder will be replaced with the values from ['data']. 6281 Please note that a placeholder will only be treated as such if there is a corresponsing element 6282 in the 'data' array. That means that if the 'data' array has only one element and 'base' and 6283 'filter' contain more than one quesion mark, all but the first quesion mark will not be replaced. 6284 </para> 6285 <para> 6286 Also note that there is another option the LDAP driver will understand: 6287 'query2xml_placeholder'. It allows you to define the placeholder used in 'base' and 'filter'. The default 6288 is a question mark ('?'). The placeholder you define can also consist of multiple chracters. 6289 </para> 6290 <para> 6291 Let's have a look at a full example: we want all entries of the class inetOrgPerson that have a cn attribute 6292 that contains the string submitted vi $_GET['cn']. Please note that the LDAP driver internally uses 6293 {@link http://pear.php.net/package/Net_LDAP/docs/latest/Net_LDAP/Net_LDAP_Util.html#methodescape_filter_value Net_LDAP_Util::escape_filter_value()} 6294 for all elements of the 'data' array to prevent injection attacks. 6295 <programlisting role="php"> 6296 <![CDATA[ 6297<?php 6298require_once 'XML/Query2XML.php'; 6299require_once 'Net/LDAP.php'; 6300 6301$query2xml = XML_Query2XML::factory(Net_LDAP::connect()); 6302 6303$dom = $query2xml->getXML( 6304 array( 6305 'data' => array(':' . $_GET['cn']), 6306 'base' => 'ou=people,dc=example,dc=com', 6307 'filter' => '(&(objectclass=inetOrgPerson)(cn=*?*))', //the question mark will be replace 6308 //with the contents of $_GET['cn'] 6309 'options' => array( 6310 'attributes' => array( //to tell the LDAP driver which columns to watch out for 6311 'cn', 6312 'mobile', 6313 ) 6314 ) 6315 ), 6316 array( 6317 'rootTag' => 'employees', 6318 'idColumn' => 'cn', 6319 'rowTag' => 'employee', 6320 'elements' => array( 6321 'cn', 6322 'mobile' 6323 ) 6324 ) 6325); 6326header('Content-Type: application/xml'); 6327 6328$dom->formatOutput = true; 6329print $dom->saveXML(); 6330?> 6331 ]]> 6332 </programlisting> 6333 if $_GET['cn'] was set to 'John' the following XML data would be produced: 6334 <programlisting role="tutorial"> 6335 <![CDATA[ 6336<?xml version="1.0" encoding="UTF-8"?> 6337<employees> 6338 <employee> 6339 <cn>John Doe</cn> 6340 <mobile>666-777-888</mobile> 6341 </employee> 6342</employees> 6343 ]]> 6344 </programlisting> 6345 </para> 6346 </refsect2> 6347 <refsect2 id="{@id optionalattributes}"> 6348 <title>Handling Optional Attributes - $options['query']['options']['attributes']</title> 6349 <para> 6350 In an LDAP directory an entry does not have to use all attributes that are 6351 provided by the entry's objectClass. In the following example the attributes 6352 'mail' and 'mobile' (among others) are missing from the first entry. 6353 <![CDATA[ 6354dn: cn=Jane Doe,ou=people,dc=example,dc=com 6355cn: Jane Doe 6356objectClass: inetOrgPerson 6357 6358dn: cn=John Doe,ou=people,dc=example,dc=com 6359cn: John Doe 6360mail: john.doe@example.com 6361mobile: 555-666-777 6362objectClass: inetOrgPerson 6363 ]]> 6364 This would potentially lead to problems 6365 as it is contrary to what is possible in a RDBMS, where every record has to have a value for each 6366 column (even if it's NULL). XML_Query2XML was primarily built with an RDBMS in mind and therefore 6367 expects all data returned by a driver to be records represented as an array of associative arrays 6368 (where each associative array uses the same keys). 6369 </para> 6370 <para> 6371 To solve this problem the columns corresponding 6372 to missing attributes have to be set to null. But to tell the LDAP driver which columns to check for, you 6373 have to use the $options['query']['options']['attributes']. (Note: $options['query'] is 6374 an alias for $options['sql']) 6375 </para> 6376 <para> 6377 $options['query']['options']['attributes'] will be used by Net_LDAP to limit the 6378 attributes returned for each record (see the 6379 {@link http://pear.php.net/manual/en/package.networking.net-ldap.search.php Net_LDAP manual}). 6380 In addition to this, XML_Query2XML's LDAP driver will look at this option to 6381 determine which columns to set to null if they do not exist in the returned records. 6382 </para> 6383 <para> 6384 Let's look at what happens if you do not specify the 'attributes' option: 6385 <programlisting role="php"> 6386 <![CDATA[ 6387<?php 6388require_once 'XML/Query2XML.php'; 6389require_once 'Net/LDAP.php'; 6390 6391/* 6392* just pass an instance of Net_LDAP to the factory method 6393* as you would do it with an MDB2 or PDO instance. 6394*/ 6395$query2xml = XML_Query2XML::factory(Net_LDAP::connect()); 6396 6397$dom = $query2xml->getXML( 6398 array( 6399 'base' => 'ou=people,dc=example,dc=com', 6400 'filter' => '(objectclass=inetOrgPerson)' 6401 //we completely omit 'options' here 6402 ), 6403 array( 6404 'rootTag' => 'employees', 6405 'idColumn' => 'cn', 6406 'rowTag' => 'employee', 6407 'elements' => array( 6408 'cn', 6409 'pager' 6410 ) 6411 ) 6412); 6413header('Content-Type: application/xml'); 6414 6415$dom->formatOutput = true; 6416print $dom->saveXML(); 6417?> 6418 ]]> 6419 </programlisting> 6420 You would get something like this: 6421 <![CDATA[ 6422Fatal error: Uncaught XML_Query2XML_ConfigException: [elements][pager]: The column "pager" was not found in the result set. 6423 ]]> 6424 This is because Jane Doe does not have a pager. As the LDAP driver was not told 6425 that the 'pager' attribute might be missing from some entries, it returned Jane 6426 Doe's record without the column 'pager'. 6427 An LDAP query specification should therefore always use the 'attributes' option! 6428 </para> 6429 </refsect2> 6430 <refsect2 id="{@id multivalueattributes}"> 6431 <title>Handling Multi-Value Attributes</title> 6432 <para> 6433 In an LDAP directory, attributes are mutli-valued, that is they can hold multiple values. 6434 This is nice because it allows you to easily store, say not just one email 6435 address, but as many as you like. This is contrary to what you can usually 6436 do in an RDBMS. As XML_Query2XML was built primarily with an RDBMS in mind, 6437 it expects simple records, each represented by a one-dimensional array to be 6438 returned by the LDAP driver. 6439 </para> 6440 <para> 6441 The LDAP driver therefore creates multiple records for each entry that 6442 has multi-value attributes. An entry like 6443 <![CDATA[ 6444## LDIF entry for "John Doe" 6445dn: cn=John Doe,ou=people,dc=example,dc=com 6446cn: John Doe 6447sn: Doe 6448mail: john@example.com 6449mail: johndoe@example.com 6450mail: john.doe@example.com 6451telephoneNumber: 555-111-222 6452telephoneNumber: 555-222-333 6453mobile: 666-777-888 6454 ]]> 6455 therefore has to be converted into multiple one-dimensional associative 6456 arrays (i.e. records): 6457 <![CDATA[ 6458 cn mail telephoneNumber mobile 6459 ------------------------------------------------------- 6460 John Doe john@example.com 555-111-222 666-777-888 6461 John Doe johndoe@example.com 555-222-333 666-777-888 6462 John Doe john.doe@example.com 555-111-222 666-777-888 6463 ]]> 6464 Note that no cartasian product of the mail-values and the mobile-values 6465 is produced (that would result in six instead of three records in the 6466 above example). The number of records returned is equal to the number 6467 values assigned to the attribute that has the most values (here 6468 it's the mail attribute that has 3 values). To make sure that every 6469 record has valid values for all attributes/columns, we continiusly loop 6470 over the available value until the attribute with the most values 6471 is done. In the above example the attribute telephoneNumber had only 6472 two values while the mail attribute had three. The record for 6473 the third mail attribute value therefore contains the first value of 6474 the telephoneNumber attribute. 6475 </para> 6476 <para> 6477 So what does that mean for $options (the second argument passed to getXML())? 6478 It means that not much changes compared to how you would define $options 6479 when using a database-related driver. As shown above, using 6480 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications} 6481 will work, but will only produce the first value for each attribute: 6482 <programlisting role="php"> 6483 <![CDATA[ 6484<?php 6485require_once 'XML/Query2XML.php'; 6486require_once 'Net/LDAP.php'; 6487 6488$query2xml = XML_Query2XML::factory(Net_LDAP::connect()); 6489 6490$dom = $query2xml->getXML( 6491 array( 6492 'base' => 'ou=people,dc=example,dc=com', 6493 'filter' => '(objectclass=inetOrgPerson)', 6494 'options' => array( 6495 'attributes' => array( //to tell the LDAP driver which columns to watch out for 6496 'cn', 6497 'mail', 6498 ) 6499 ) 6500 ), 6501 array( 6502 'rootTag' => 'employees', 6503 'idColumn' => 'cn', 6504 'rowTag' => 'employee', 6505 'elements' => array( 6506 'cn', //simple element specification 6507 'mail' //simple element specification 6508 ) 6509 ) 6510); 6511header('Content-Type: application/xml'); 6512 6513$dom->formatOutput = true; 6514print $dom->saveXML(); 6515?> 6516 ]]> 6517 </programlisting> 6518 notice how the resulting XML only shows the first value of the mail attribute: 6519 <programlisting role="tutorial"> 6520 <![CDATA[ 6521<?xml version="1.0" encoding="UTF-8"?> 6522<employees> 6523 <employee> 6524 <cn>John Doe</cn> 6525 <mail>john@example.com</mail> 6526 </employee> 6527 <employee> 6528 <cn>Jane Doe</cn> 6529 <mail>jane@example.com</mail> 6530 </employee> 6531</employees> 6532 ]]> 6533 </programlisting> 6534 To get a "mail" XML element for each value of the "mail" LDAP attribute, we have 6535 to use {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.complex_element_specifications} 6536 and set {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_idcolumn}: 6537 <programlisting role="php"> 6538 <![CDATA[ 6539<?php 6540require_once 'XML/Query2XML.php'; 6541require_once 'Net/LDAP.php'; 6542 6543$query2xml = XML_Query2XML::factory(Net_LDAP::connect()); 6544 6545$dom = $query2xml->getXML( 6546 array( 6547 'base' => 'ou=people,dc=example,dc=com', 6548 'filter' => '(objectclass=inetOrgPerson)', 6549 'options' => array( 6550 'attributes' => array( //to tell the LDAP driver which columns to watch out for 6551 'cn', 6552 'mail', 6553 ) 6554 ) 6555 ), 6556 array( 6557 'rootTag' => 'employees', 6558 'idColumn' => 'cn', 6559 'rowTag' => 'employee', 6560 'elements' => array( 6561 'cn', //simple element specification 6562 'mail' => array( 6563 'idColumn' => 'mail', 6564 'value' => 'mail' 6565 ) 6566 ) 6567 ) 6568); 6569header('Content-Type: application/xml'); 6570 6571$dom->formatOutput = true; 6572print $dom->saveXML(); 6573?> 6574 ]]> 6575 </programlisting> 6576 This produces the following XML data: 6577 <programlisting role="tutorial"> 6578 <![CDATA[ 6579<?xml version="1.0" encoding="UTF-8"?> 6580<employees> 6581 <employee> 6582 <cn>John Doe</cn> 6583 <mail>john@example.com</mail> 6584 <mail>johndoe@example.com</mail> 6585 <mail>john.doe@example.com</mail> 6586 </employee> 6587 <employee> 6588 <cn>Jane Doe</cn> 6589 <mail>jane@example.com</mail> 6590 <mail>jane.doe@example.com</mail> 6591 </employee> 6592</employees> 6593 ]]> 6594 </programlisting> 6595 This is because we set {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_idcolumn} 6596 within the complex element specification to "mail". Every record with a unique value 6597 for the column "mail" is therefore processed. 6598 </para> 6599 <para> 6600 As all LDAP attributes can have multiple values it is therefore highly recommended to 6601 always use a complex element specification with $options['idColumn'] as described above. 6602 The only exception is when you just want a single value (and don't care which one). 6603 </para> 6604 </refsect2> 6605 </refsect1> 6606 6607 <refsect1 id="{@id multipledrivers}"> 6608 <title>Using Multiple Drivers</title> 6609 <para> 6610 There might be a situation where you want to genearte your XML data from multiple RDBMSes or 6611 an RDBMS and an LDAP server. By using the option 'driver' within a 6612 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql.complexqueryspecification} or a 6613 {@tutorial XML_Query2XML.pkg#ldap.queryspecification} you can specify any driver you want. 6614 To create an instance of one of the drivers that come with XML_Query2XML use 6615 {@link XML_Query2XML_Driver::factory()}. If you want to write your own driver, please see 6616 {@tutorial XML_Query2XML.pkg#yourdriver}. Let's start with an example that pulls data 6617 from a MySQL and a ProstgreSQL database: 6618 <programlisting role="php"> 6619 <![CDATA[ 6620<?php 6621require_once 'XML/Query2XML.php'; 6622require_once 'MDB2.php'; 6623 6624$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 6625 6626$pgsqlDriver = XML_Query2XML_Driver::factory(MDB2::factory('pgsql://postgres:test@localhost/query2xml_tests')); 6627$dom = $query2xml->getXML( 6628 'SELECT * FROM artist', 6629 array( 6630 'rootTag' => 'artists', 6631 'idColumn' => 'artistid', 6632 'rowTag' => 'artist', 6633 'elements' => array( 6634 'name', 6635 'genre', 6636 'album' => array( 6637 'sql' => array( 6638 'driver' => $pgsqlDriver, 6639 'data' => array( 6640 'artistid' 6641 ), 6642 'query' => 'SELECT * FROM album WHERE artist_id = ?' 6643 ), 6644 'idColumn' => 'albumid', 6645 'elements' => array( 6646 'title', 6647 'published_year' 6648 ) 6649 ) 6650 ) 6651 ) 6652); 6653header('Content-Type: application/xml'); 6654 6655$dom->formatOutput = true; 6656print $dom->saveXML(); 6657?> 6658 ]]> 6659 </programlisting> 6660 The resulting XML being: 6661 <programlisting role="tutorial"> 6662 <![CDATA[ 6663<?xml version="1.0" encoding="UTF-8"?> 6664<artists> 6665 <artist> 6666 <name>Curtis Mayfield</name> 6667 <genre>Soul</genre> 6668 <album> 6669 <title>New World Order</title> 6670 <published_year>1990</published_year> 6671 </album> 6672 <album> 6673 <title>Curtis</title> 6674 <published_year>1970</published_year> 6675 </album> 6676 </artist> 6677 <artist> 6678 <name>Isaac Hayes</name> 6679 <genre>Soul</genre> 6680 <album> 6681 <title>Shaft</title> 6682 <published_year>1972</published_year> 6683 </album> 6684 </artist> 6685 <artist> 6686 <name>Ray Charles</name> 6687 <genre>Country and Soul</genre> 6688 </artist> 6689</artists> 6690 ]]> 6691 </programlisting> 6692 </para> 6693 <para> 6694 Now let's combine an RDBMS with the data from an LDAP server: 6695 <programlisting role="php"> 6696 <![CDATA[ 6697<?php 6698require_once 'XML/Query2XML.php'; 6699require_once 'Net/LDAP.php'; 6700require_once 'MDB2.php'; 6701$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 6702 6703$dom = $query2xml->getXML( 6704 "SELECT * FROM employee", 6705 array( 6706 'rootTag' => 'employees', 6707 'idColumn' => 'employeeid', 6708 'rowTag' => 'employee', 6709 'elements' => array( 6710 'name' => 'employeename', 6711 'contact_details' => array( 6712 'query' => array( 6713 'driver' => XML_Query2XML_Driver::factory(Net_LDAP::connect()), 6714 'data' => array( 6715 'employeename' 6716 ), 6717 'base' => 'ou=people,dc=example,dc=com', 6718 'filter' => '(&(objectclass=inetOrgPerson)(cn=?))', 6719 'options' => array( 6720 'attributes' => array( 6721 'cn', 6722 'telephoneNumber', 6723 'mobile', 6724 'mail', 6725 'labeledURI' 6726 ) 6727 ) 6728 ), 6729 'idColumn' => 'cn', 6730 'elements' => array( 6731 'telephoneNumber' => array( 6732 'idColumn' => 'telephoneNumber', 6733 'value' => 'telephoneNumber' 6734 ), 6735 'mobile' => array( 6736 'idColumn' => 'mobile', 6737 'value' => 'mobile' 6738 ), 6739 'mail' => array( 6740 'idColumn' => 'mail', 6741 'value' => 'mail' 6742 ), 6743 'labeledURI' => array( 6744 'idColumn' => 'labeledURI', 6745 'value' => 'labeledURI' 6746 ) 6747 ) 6748 ) 6749 ), 6750 ) 6751); 6752$dom->formatOutput = true; 6753print $dom->saveXML(); 6754?> 6755 ]]> 6756 </programlisting> 6757 As we only have corresponsing entries in the LDAP directory for two of our 6758 employees in the mysql database the resulting XML looks like this: 6759 <programlisting role="tutorial"> 6760 <![CDATA[ 6761<?xml version="1.0" encoding="UTF-8"?> 6762<employees> 6763 <employee> 6764 <name>Michael Jones</name> 6765 </employee> 6766 <employee> 6767 <name>Susi Weintraub</name> 6768 <contact_details> 6769 <telephoneNumber>555-111-222</telephoneNumber> 6770 <mobile>555-666-777</mobile> 6771 <mobile>555-777-888</mobile> 6772 <mail>susi@example.com</mail> 6773 <labeledURI>http://susi.example.com</labeledURI> 6774 <labeledURI>http://susiweintraub.example.com</labeledURI> 6775 <labeledURI>http://susi.weintraub.example.com</labeledURI> 6776 </contact_details> 6777 </employee> 6778 <employee> 6779 <name>Steve Hack</name> 6780 </employee> 6781 <employee> 6782 <name>Joan Kerr</name> 6783 </employee> 6784 <employee> 6785 <name>Marcus Roth</name> 6786 </employee> 6787 <employee> 6788 <name>Jack Mack</name> 6789 </employee> 6790 <employee> 6791 <name>Rita Doktor</name> 6792 </employee> 6793 <employee> 6794 <name>David Til</name> 6795 </employee> 6796 <employee> 6797 <name>Pia Eist</name> 6798 </employee> 6799 <employee> 6800 <name>Hanna Poll</name> 6801 </employee> 6802 <employee> 6803 <name>Jim Wells</name> 6804 <contact_details> 6805 <telephoneNumber>555-444-888</telephoneNumber> 6806 <mobile>555-666-777</mobile> 6807 <mail>jim@example.com</mail> 6808 <mail>jim.wells@example.com</mail> 6809 <mail>jimwells@example.com</mail> 6810 <mail>jwells@example.com</mail> 6811 <labeledURI>http://jimwells.example.com</labeledURI> 6812 <labeledURI>http://jim.wells.example.com</labeledURI> 6813 </contact_details> 6814 </employee> 6815 <employee> 6816 <name>Sandra Wilson</name> 6817 </employee> 6818</employees> 6819 ]]> 6820 </programlisting> 6821 </para> 6822 </refsect1> 6823 6824 <refsect1 id="{@id x_to_n}"> 6825 <title>Implementing 1:N and N:N relationships</title> 6826 <para> 6827 Often times you will have a one-to-many (1:N) or many-to-many (N:N) relationship between 6828 two tables. For example, the 6829 {@link http://query2xml.sourceforge.net/docs/Query2XML_Tests.jpg ER diagram} 6830 of our sample applications shows an 1:N relationship between the tables artist and album 6831 (<emphasis>one</emphasis> artist can perform <emphasis>many</emphasis> albums). For 6832 the sake of simplicity we will be using this 1:N relationship in this section (note that 6833 the intersection table employee_department implements an N:N relationship between employee 6834 and department). 6835 </para> 6836 <para> 6837 When dealing with 1:N or N:N relationships there are two basic questions that determine 6838 how you will use XML_Query2XML to implement your solution: 6839 <itemizedlist> 6840 <listitem> 6841 Do you want to use {@tutorial XML_Query2XML.pkg#x_to_n.one_or_many_queries} 6842 </listitem> 6843 <listitem> 6844 Do you want to produce {@tutorial XML_Query2XML.pkg#x_to_n.one_or_many_elements} 6845 </listitem> 6846 </itemizedlist> 6847 </para> 6848 <refsect2 id="{@id one_or_many_queries}"> 6849 <title>One or Many Queries?</title> 6850 <para> 6851 In almost every situation you will be better of using one big query than running 6852 multiple smaller queries. Anyway, XML_Query2XML gives you the option of 6853 <itemizedlist> 6854 <listitem> 6855 {@tutorial XML_Query2XML.pkg#x_to_n.one_or_many_queries.join} (one query) 6856 </listitem> 6857 <listitem> 6858 {@tutorial XML_Query2XML.pkg#x_to_n.one_or_many_queries.options_sql} (multiple queries) 6859 </listitem> 6860 </itemizedlist> 6861 </para> 6862 <refsect3 id="{@id join}"> 6863 <title>Using an SQL JOIN</title> 6864 <para> 6865 The best way to query two tables is to JOIN them together using an SQL JOIN. 6866 If you are new to SQL check out first what wikipedia has to say about {@link http://en.wikipedia.org/wiki/Join_(SQL) Joins}. 6867 I could also recommend 6868 {@link http://www.amazon.com/Learning-SQL-Alan-Beaulieu/dp/0596007272/ref=pd_bbs_sr_1?ie=UTF8&s=books&qid=1206554386&sr=1-1 Learning SQL} 6869 by Alan Beaulieu. 6870 </para> 6871 <para> 6872 Use {@link XML_Query2XML::getXML()}'s first argument {@tutorial XML_Query2XML.pkg#query2xml_getxml.sql} 6873 to pass the SQL JOIN to XML_Query2XML. 6874 </para> 6875 <para> 6876 A very important thing to keep in mind is the correct specification of 6877 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_idcolumn}. On 6878 the root level it is set to 'artistid' (the parent entity's primary key) while 6879 on the level of $options['elements']['albums'] it is set to 'albumid' 6880 (the child entity's primary key). Here goes the code: 6881 <programlisting role="php"> 6882 <![CDATA[ 6883<?php 6884require_once 'XML/Query2XML.php'; 6885require_once 'MDB2.php'; 6886$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 6887$dom = $query2xml->getXML( 6888 'SELECT 6889 * 6890 FROM 6891 artist, album 6892 WHERE 6893 album.artist_id = artist.artistid', 6894 array( 6895 'rootTag' => 'music_library', 6896 'rowTag' => 'artist', 6897 'idColumn' => 'artistid', 6898 'elements' => array( 6899 'artistid', 6900 'name', 6901 'albums' => array( 6902 'rootTag' => 'albums', 6903 'rowTag' => 'album', 6904 'idColumn' => 'albumid', 6905 'elements' => array( 6906 'albumid', 6907 'title' 6908 ) 6909 ) 6910 ) 6911 ) 6912); 6913 6914header('Content-Type: application/xml'); 6915 6916$dom->formatOutput = true; 6917print $dom->saveXML(); 6918?> 6919 ]]> 6920 </programlisting> 6921 And the resulting XML data looks as follows: 6922 <programlisting role="tutorial"> 6923 <![CDATA[ 6924<?xml version="1.0" encoding="UTF-8"?> 6925<music_library> 6926 <artist> 6927 <artistid>1</artistid> 6928 <name>Curtis Mayfield</name> 6929 <albums> 6930 <album> 6931 <albumid>1</albumid> 6932 <title>New World Order</title> 6933 </album> 6934 <album> 6935 <albumid>2</albumid> 6936 <title>Curtis</title> 6937 </album> 6938 </albums> 6939 </artist> 6940 <artist> 6941 <artistid>2</artistid> 6942 <name>Isaac Hayes</name> 6943 <albums> 6944 <album> 6945 <albumid>3</albumid> 6946 <title>Shaft</title> 6947 </album> 6948 </albums> 6949 </artist> 6950</music_library> 6951 ]]> 6952 </programlisting> 6953 Note: as we used a traditional join (and therefore implecitely an INNER JOIN) only those 6954 artists are included that also have albums in our database. To also get those artists 6955 that don't have any albums we would have to use a LEFT OUTER JOIN. 6956 </para> 6957 </refsect3> 6958 <refsect3 id="{@id options_sql}"> 6959 <title>Using $options['sql']</title> 6960 <para> 6961 If for whatever reason you cannot join the two tables, you can use 6962 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql} to specify 6963 a separate query for the second table. This will however result in the 6964 second query being executed as many times as the there are records in 6965 the first table. It is therefore highly recommended to use a JOIN 6966 whenever possible. 6967 </para> 6968 <para> 6969 To produce a similar XML as with the JOIN above we have to use a 6970 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql.complexqueryspecification} 6971 with artist.artistid for $options['elements']['albums']['sql']['data'] and 6972 'SELECT * FROM album WHERE artist_id = ?' for $options['elements']['albums']['sql']['query']: 6973 <programlisting role="php"> 6974 <![CDATA[ 6975<?php 6976require_once 'XML/Query2XML.php'; 6977require_once 'MDB2.php'; 6978$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 6979$dom = $query2xml->getXML( 6980 'SELECT * FROM artist', 6981 array( 6982 'rootTag' => 'music_library', 6983 'rowTag' => 'artist', 6984 'idColumn' => 'artistid', 6985 'elements' => array( 6986 'artistid', 6987 'name', 6988 'albums' => array( 6989 'rootTag' => 'albums', 6990 'rowTag' => 'album', 6991 'idColumn' => 'albumid', 6992 'sql' => array( 6993 'data' => array( 6994 'artistid' 6995 ), 6996 'query' => 'SELECT * FROM album WHERE artist_id = ?' 6997 ), 6998 'elements' => array( 6999 'albumid', 7000 'title' 7001 ) 7002 ) 7003 ) 7004 ) 7005); 7006 7007header('Content-Type: application/xml'); 7008 7009$dom->formatOutput = true; 7010print $dom->saveXML(); 7011?> 7012 ]]> 7013 </programlisting> 7014 Note that $options['elements']['albums']['idColumn'] is again set to 'albumid'. 7015 </para> 7016 <para> 7017 The difference to the above XML is that it also includes artists that have no albums in our database. 7018 This is because using two separate queries is like using an OUTER JOIN while in the above 7019 example we were using an INNER JOIN. 7020 <programlisting role="tutorial"> 7021 <![CDATA[ 7022<?xml version="1.0" encoding="UTF-8"?> 7023<music_library> 7024 <artist> 7025 <artistid>1</artistid> 7026 <name>Curtis Mayfield</name> 7027 <albums> 7028 <album> 7029 <albumid>1</albumid> 7030 <title>New World Order</title> 7031 </album> 7032 <album> 7033 <albumid>2</albumid> 7034 <title>Curtis</title> 7035 </album> 7036 </albums> 7037 </artist> 7038 <artist> 7039 <artistid>2</artistid> 7040 <name>Isaac Hayes</name> 7041 <albums> 7042 <album> 7043 <albumid>3</albumid> 7044 <title>Shaft</title> 7045 </album> 7046 </albums> 7047 </artist> 7048 <artist> 7049 <artistid>3</artistid> 7050 <name>Ray Charles</name> 7051 <albums/> 7052 </artist> 7053</music_library> 7054 ]]> 7055 </programlisting> 7056 </para> 7057 </refsect3> 7058 </refsect2> 7059 <refsect2 id="{@id one_or_many_elements}"> 7060 <title>One or Many Elements Per Related Entity?</title> 7061 <para> 7062 Depending on whether you want one or multiple XML element per related entity, 7063 you have two options here: 7064 <itemizedlist> 7065 <listitem> 7066 {@tutorial XML_Query2XML.pkg#x_to_n.one_or_many_elements.options_elements}. 7067 </listitem> 7068 <listitem> 7069 {@tutorial XML_Query2XML.pkg#x_to_n.one_or_many_elements.options_value}. 7070 </listitem> 7071 </itemizedlist> 7072 </para> 7073 <refsect3 id="{@id options_elements}"> 7074 <title>Multiple Elements Per Entity - $options['elements']</title> 7075 <para> 7076 In most cases you will need to create multiple XML elements for each 7077 related entity. In our example we might want the albumid and the title 7078 for each album. This means that we have to assign 7079 <programlisting role="php"> 7080 <![CDATA[ 7081array( 7082 'albumid', 7083 'title' 7084); 7085 ]]> 7086 </programlisting> 7087 to $options['elements']['albums']['elements']. 7088 </para> 7089 <para> 7090 Please see the {@tutorial XML_Query2XML.pkg#x_to_n.one_or_many_queries} section above 7091 for two example - one using a JOIN and the second using two queries. 7092 </para> 7093 </refsect3> 7094 <refsect3 id="{@id options_value}"> 7095 <title>One Element Per Entity - $options['value']</title> 7096 <para> 7097 In our example, if you want just a single XML element for each album like below, 7098 we can use {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_value}. 7099 <programlisting role="tutorial"> 7100 <![CDATA[ 7101<?xml version="1.0" encoding="UTF-8"?> 7102<music_library> 7103 <artist> 7104 <name>Curtis Mayfield</name> 7105 <album>New World Order</album> 7106 <album>Curtis</album> 7107 </artist> 7108 <artist> 7109 <name>Isaac Hayes</name> 7110 <album>Shaft</album> 7111 </artist> 7112</music_library> 7113 ]]> 7114 </programlisting> 7115 The code to generate the above XML would look like this: 7116 <programlisting role="php"> 7117 <![CDATA[ 7118<?php 7119require_once 'XML/Query2XML.php'; 7120require_once 'MDB2.php'; 7121$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 7122$dom = $query2xml->getXML( 7123 'SELECT 7124 * 7125 FROM 7126 artist, album 7127 WHERE 7128 album.artist_id = artist.artistid', 7129 array( 7130 'rootTag' => 'music_library', 7131 'rowTag' => 'artist', 7132 'idColumn' => 'artistid', 7133 'elements' => array( 7134 'name', 7135 'album' => array( 7136 'idColumn' => 'albumid', 7137 'value' => 'title' 7138 ) 7139 ) 7140 ) 7141); 7142 7143header('Content-Type: application/xml'); 7144 7145$dom->formatOutput = true; 7146print $dom->saveXML(); 7147?> 7148 ]]> 7149 </programlisting> 7150 The critical part here is $options['elements']['albums']['idColumn']. 7151 It is set to 'albumid'. This means that an XML element will be created 7152 for $options['elements']['albums'] for each unique albumid. 7153 </para> 7154 <para> 7155 The above example uses a JOIN which is most efficient. 7156 But as shown above under 7157 {@tutorial XML_Query2XML.pkg#x_to_n.one_or_many_queries.options_sql} 7158 there might be situations where you want to use multiple SQL queries 7159 instead of a single JOIN: 7160 <programlisting role="php"> 7161 <![CDATA[ 7162<?php 7163require_once 'XML/Query2XML.php'; 7164require_once 'MDB2.php'; 7165$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 7166$dom = $query2xml->getXML( 7167 'SELECT * FROM artist', 7168 array( 7169 'rootTag' => 'music_library', 7170 'rowTag' => 'artist', 7171 'idColumn' => 'artistid', 7172 'elements' => array( 7173 'name', 7174 'album' => array( 7175 'sql' => array( 7176 'data' => array( 7177 'artistid' 7178 ), 7179 'query' => 'SELECT * FROM album WHERE artist_id = ?' 7180 ), 7181 'idColumn' => 'albumid', 7182 'value' => 'title' 7183 ) 7184 ) 7185 ) 7186); 7187 7188header('Content-Type: application/xml'); 7189 7190$dom->formatOutput = true; 7191print $dom->saveXML(); 7192?> 7193 ]]> 7194 </programlisting> 7195 The resulting XML is similar to the one generated above. There is just 7196 one difference: artists that have no albums are included as well: 7197 <programlisting role="tutorial"> 7198 <![CDATA[ 7199<?xml version="1.0" encoding="UTF-8"?> 7200<music_library> 7201 <artist> 7202 <name>Curtis Mayfield</name> 7203 <album>New World Order</album> 7204 <album>Curtis</album> 7205 </artist> 7206 <artist> 7207 <name>Isaac Hayes</name> 7208 <album>Shaft</album> 7209 </artist> 7210 <artist> 7211 <name>Ray Charles</name> 7212 </artist> 7213</music_library> 7214 ]]> 7215 </programlisting> 7216 </para> 7217 </refsect3> 7218 </refsect2> 7219 <refsect2 id="{@id joins}"> 7220 <title>A last word on JOINs</title> 7221 <para> 7222 As a general rule, it is wise to use as few SQL queries as possible. 7223 This means that you have to JOIN you tables. It is essential to understand 7224 the differences between INNER and OUTER joins and how they affect your 7225 result set. 7226 </para> 7227 </refsect2> 7228 </refsect1> 7229 7230 <refsect1 id="{@id yourdriver}"> 7231 <title>Writing Your Own Driver</title> 7232 <para> 7233 If you want to work with a primary data source other than 7234 PDO, MDB2, DB, ADOdb or Net_LDAP, you might want to write 7235 your own driver. 7236 </para> 7237 <para> 7238 Start out by extending the abstract class 7239 {@link XML_Query2XML_Driver} and implementing its abstract method 7240 getAllRecords(): 7241 <programlisting role="php"> 7242 <![CDATA[ 7243/**Contains the abstract class XML_Query2XML_Driver and the 7244* exception class XML_Query2XML_DBException. 7245*/ 7246require_once 'XML/Query2XML.php'; 7247 7248class MyFirebirdDriver extends XML_Query2XML_Driver 7249{ 7250 /*This method is defined as an abstract method within 7251 * XML_Query2XML_Driver and therefore has to be implemented. 7252 */ 7253 public function getAllRecords($sql, $configPath) 7254 { 7255 } 7256} 7257?> 7258 ]]> 7259 </programlisting> 7260 For the sake of example, we'll be building a Firebird driver using 7261 PHP's native interbase API. 7262 </para> 7263 <para> 7264 Now let's add a constructor method that accepts the arguments needed to 7265 set up an Interbase/Firebird connection: 7266 <programlisting role="php"> 7267 <![CDATA[ 7268<?php 7269public function __construct($database, $username, $password) 7270{ 7271 $this->_dbh = @ibase_pconnect($database, $username, $password); 7272 if ($this->_dbh === false) { 7273 throw new XML_Query2XML_DBException( 7274 'Could not connect to database: ' . ibase_errmsg() 7275 . '(error code: ' . ibase_errcode() . ')' 7276 ); 7277 } 7278} 7279?> 7280 ]]> 7281 </programlisting> 7282 </para> 7283 <para> 7284 Before we code the body of our getAllRecords() method, we have to 7285 decide whether to overwrite the method {@link XML_Query2XML_Driver::preprocessQuery()}. 7286 This method does two things: 7287 <itemizedlist> 7288 <listitem> 7289 pre-process {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql} 7290 </listitem> 7291 <listitem> 7292 return the query statement as a string that will be used by XML_Query2XML 7293 for logging, profiling and caching 7294 </listitem> 7295 </itemizedlist> 7296 The importance of preprocessQuery() lies in the fact that it determines 7297 the format of {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql}. 7298 As implemented in XML_Query2XML_Driver $options['sql'] can be either 7299 a string or an array containing the element 'query'. If it is a string 7300 it will be transformed to an array with the element 'query' holding 7301 the string. This means that getAllRecords() only needs to deal with 7302 a first argument that is a string and has a 'query' element. In this 7303 example we will not overwrite preprocessQuery(). 7304 </para> 7305 <para> 7306 One aspect that is not controlled by preprocessQuery() is $options['sql']['data']. 7307 If it is set, XML_Query2XML requires it to be an array of strings or instances of 7308 classes that implement XML_Query2XML_Callback. The evaluations of the specifications 7309 in $options['sql']['data'] are entirely handled by XML_Query2XML. 7310 </para> 7311 <para> 7312 All that is left now is writing the body of the getAllRecords() method. 7313 Regarding its first argument, {@link XML_Query2XML_Driver::preprocessQuery()} enforces the following rules 7314 <itemizedlist> 7315 <listitem> 7316 It will be an associative array. 7317 </listitem> 7318 <listitem> 7319 The array will have a key named 'query'. 7320 </listitem> 7321 </itemizedlist> 7322 </para> 7323 <para> 7324 A first version of getAllRecords() might therefore look like this: 7325 <programlisting role="php"> 7326 <![CDATA[ 7327<?php 7328public function getAllRecords($sql, $configPath) 7329{ 7330 if (!isset($sql['data'])) { 7331 //do not presume that $sql['data'] is present 7332 $sql['data'] = array(); 7333 } 7334 7335 //prepare 7336 $statement = @ibase_prepare($this->_dbh, $sql['query']); 7337 7338 //execute 7339 $args = $sql['data']; 7340 array_unshift($args, $statement); 7341 $result = call_user_func_array('ibase_execute', $args); 7342 7343 //fetch all records 7344 while ($record = ibase_fetch_assoc($result, IBASE_TEXT)) { 7345 $records[] = $record; 7346 } 7347 ibase_free_result($result); 7348 7349 return $records; 7350} 7351?> 7352 ]]> 7353 </programlisting> 7354 The above code is already functional but is still missings some important 7355 error checking. Below is the final version of our MyFirebirdDriver class, 7356 that includes a more robust version of getAllRecords(). Note how getAllRecords()'s 7357 second argument, $configPath is used for the exception messages. 7358 <programlisting role="php"> 7359 <![CDATA[ 7360<?php 7361/**Contains the abstract class XML_Query2XML_Driver and the 7362* exception class XML_Query2XML_DBException. 7363*/ 7364require 'XML/Query2XML.php'; 7365class MyFirebirdDriver extends XML_Query2XML_Driver 7366{ 7367 private $_dhb = null; 7368 7369 public function __construct($database, $username, $password) 7370 { 7371 $this->_dbh = @ibase_pconnect($database, $username, $password); 7372 if ($this->_dbh === false) { 7373 throw new XML_Query2XML_DBException( 7374 'Could not connect to database: ' . ibase_errmsg() 7375 . '(error code: ' . ibase_errcode() . ')' 7376 ); 7377 } 7378 } 7379 7380 /**Returns are records retrieved by running an SQL query. 7381 * @param mixed $sql A query string or an array with the elements 7382 * 'query' (and optionally 'data'). 7383 * @param string $configPath Where in $options the query was defined. 7384 * @return array All records as a two-dimensional array. 7385 */ 7386 public function &getAllRecords($sql, $configPath) 7387 { 7388 if (!isset($sql['data'])) { 7389 //do not presume that $sql['data'] is present 7390 $sql['data'] = array(); 7391 } 7392 7393 /* 7394 * prepare 7395 */ 7396 $statement = @ibase_prepare($this->_dbh, $sql['query']); 7397 if ($statement === false) { 7398 throw new XML_Query2XML_DBException( 7399 /* 7400 * Note how we use $configPath here. This will make 7401 * the exception message tell you where in $options 7402 * the error needs to be fixed. 7403 */ 7404 $configPath . ': Could not prepare query "' . $sql['query'] . '": ' 7405 . ibase_errmsg() . '(error code: ' . ibase_errcode() . ')' 7406 ); 7407 } 7408 7409 /* 7410 * execute 7411 */ 7412 $args = $sql['data']; 7413 array_unshift($args, $statement); 7414 $result = call_user_func_array( 7415 'ibase_execute', 7416 $args 7417 ); 7418 if ($result === false) { 7419 throw new XML_Query2XML_DBException( 7420 /* 7421 * Note again how we use $configPath here. 7422 */ 7423 $configPath . ': Could not execute query: "' . $sql['query'] . '": ' 7424 . ibase_errmsg() . '(error code: ' . ibase_errcode() . ')' 7425 ); 7426 } elseif ($result === true) { 7427 //empty result set 7428 $records = array(); 7429 } else { 7430 //fetch all records 7431 while ($record = ibase_fetch_assoc($result, IBASE_TEXT)) { 7432 $records[] = $record; 7433 } 7434 ibase_free_result($result); 7435 } 7436 7437 /* 7438 * return a two dimensional array: numeric indexes in the first 7439 * and associative arrays in the second dimension 7440 */ 7441 return $records; 7442 } 7443} 7444 7445//test the driver directly 7446$driver = new MyFirebirdDriver('localhost:/tmp/test.fdb', 'SYSDBA', 'test'); 7447$records = $driver->getAllRecords( 7448 array( 7449 'data' => array(1), 7450 'query' => 'SELECT * FROM test WHERE id = ?' 7451 ), 7452 '[config]' 7453); 7454print_r($records); 7455?> 7456 ]]> 7457 </programlisting> 7458 </para> 7459 <para> 7460 Using XML_Query2XML_DBException for a database-related driver makes sense of course. 7461 But when your driver has nothing to do with an RDBMS, it is recommended to create 7462 your own exception class by extending {@link XML_Query2XML_DriverException}: 7463<programlisting role="php"> 7464 <![CDATA[ 7465<?php 7466/**Exception for MyDriver errors 7467*/ 7468class MyDriverException extends XML_Query2XML_DriverException 7469{ 7470 /**Constructor 7471 * @param string $message The error message. 7472 */ 7473 public function __construct($message) 7474 { 7475 parent::__construct($message); 7476 } 7477} 7478?> 7479 ]]> 7480 </programlisting> 7481 Note: it is in no way required but I would recommend that 7482 all exceptions your driver throws extend {@link XML_Query2XML_DriverException}. That way 7483 you can catch driver related exceptions in a consistent way - no matter what driver is used. 7484 </para> 7485 </refsect1> 7486 7487 <refsect1 id="{@id casestudies}"> 7488 <title>Case Studies</title> 7489 <para> 7490 Now let's have a look at some of XML_Query2XML's features in action. We'll start out with simple cases. 7491 We'll turn to rather complex ones as we proceed. All cases are included in the source distribution. 7492 Each case has its own directory cases/caseXX and will consist of 5 files (Case 01 contains only 7493 the first 2): 7494 <itemizedlist> 7495 <listitem> 7496 <emphasis>caseXX.php</emphasis>: generates the XML data. 7497 </listitem> 7498 <listitem> 7499 <emphasis>caseXX.xml</emphasis>: the generated the XML data saved to a file. 7500 </listitem> 7501 <listitem> 7502 <emphasis>caseXX_debug.php</emphasis>: does debugging and profiling and generates 7503 caseXX.log and caseXX.profile. 7504 </listitem> 7505 <listitem> 7506 <emphasis>caseXX.log</emphasis>: the generated debug log 7507 </listitem> 7508 <listitem> 7509 <emphasis>caseXX.profile</emphasis>: the generated profile 7510 </listitem> 7511 </itemizedlist> 7512 </para> 7513 <para> 7514 The SQL DDL used in all cases can be found in tests/Query2XML_Tests.sql and 7515 {@tutorial XML_Query2XML.pkg#sqlddl}. 7516 </para> 7517 <refsect2 id="{@id case01}"> 7518 <title>Case 01: simple SELECT with getFlatXML</title> 7519 <para> 7520 Case 01 will teach you: 7521 <itemizedlist> 7522 <listitem> 7523 How to use {@link XML_Query2XML::getFlatXML()}. 7524 </listitem> 7525 </itemizedlist> 7526 </para> 7527 <para> 7528 Case 01 is as simple as it can get. We use {@link XML_Query2XML::getFlatXML()} to generate 7529 flat XML data. 7530 </para> 7531 7532 <refsect3 id="{@id case01_php}"> 7533 <title>case01.php</title> 7534 <para> 7535 <programlisting role="php"> 7536 <![CDATA[ 7537<?php 7538require_once 'XML/Query2XML.php'; 7539require_once 'MDB2.php'; 7540$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 7541$dom = $query2xml->getFlatXML( 7542 "SELECT 7543 * 7544 FROM 7545 artist", 7546 'music_library', 7547 'artist'); 7548 7549header('Content-Type: application/xml'); 7550 7551$dom->formatOutput = true; 7552print $dom->saveXML(); 7553?> 7554 ]]> 7555 </programlisting> 7556 </para> 7557 </refsect3> 7558 <refsect3 id="{@id case01_xml}"> 7559 <title>case01.xml</title> 7560 <para> 7561 The result looks like this: 7562 <programlisting role="tutorial"> 7563 <![CDATA[ 7564<?xml version="1.0" encoding="UTF-8"?> 7565<music_library> 7566 <artist> 7567 <artistid>1</artistid> 7568 <name>Curtis Mayfield</name> 7569 <birth_year>1920</birth_year> 7570 <birth_place>Chicago</birth_place> 7571 <genre>Soul</genre> 7572 </artist> 7573 <artist> 7574 <artistid>2</artistid> 7575 <name>Isaac Hayes</name> 7576 <birth_year>1942</birth_year> 7577 <birth_place>Tennessee</birth_place> 7578 <genre>Soul</genre> 7579 </artist> 7580 <artist> 7581 <artistid>3</artistid> 7582 <name>Ray Charles</name> 7583 <birth_year>1930</birth_year> 7584 <birth_place>Mississippi</birth_place> 7585 <genre>Country and Soul</genre> 7586 </artist> 7587</music_library> 7588 ]]> 7589 </programlisting> 7590 </para> 7591 </refsect3> 7592 </refsect2> 7593 7594 <refsect2 id="{@id case02}"> 7595 <title>Case 02: LEFT OUTER JOIN</title> 7596 <para> 7597 Case 02 will teach you: 7598 <itemizedlist> 7599 <listitem> 7600 The basics of {@link XML_Query2XML::getXML()}. 7601 </listitem> 7602 <listitem> 7603 How to use {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_idcolumn}. 7604 </listitem> 7605 <listitem> 7606 How to use {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_roottag}. 7607 </listitem> 7608 <listitem> 7609 How to use {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_rowtag}. 7610 </listitem> 7611 <listitem> 7612 The basics of {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.complex_element_specifications}. 7613 </listitem> 7614 </itemizedlist> 7615 </para> 7616 <para> 7617 Once you have to deal with LEFT JOINs and similar "complex" SQL queries, you have to use 7618 {@link XML_Query2XML::getXML()}. The challenge is to get the $options array 7619 (getXML's second argument) right: 7620 </para> 7621 <refsect3 id="{@id case02_php}"> 7622 <title>case02.php</title> 7623 <para> 7624 case02.php looks like this: 7625 <programlisting role="php"> 7626 <![CDATA[ 7627<?php 7628require_once 'XML/Query2XML.php'; 7629require_once 'MDB2.php'; 7630$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 7631$dom = $query2xml->getXML( 7632 "SELECT 7633 * 7634 FROM 7635 artist 7636 LEFT JOIN album ON album.artist_id = artist.artistid", 7637 array( 7638 'rootTag' => 'music_library', 7639 'rowTag' => 'artist', 7640 'idColumn' => 'artistid', 7641 'elements' => array( 7642 'artistid', 7643 'name', 7644 'birth_year', 7645 'birth_place', 7646 'genre', 7647 'albums' => array( 7648 'rootTag' => 'albums', 7649 'rowTag' => 'album', 7650 'idColumn' => 'albumid', 7651 'elements' => array( 7652 'albumid', 7653 'title', 7654 'published_year', 7655 'comment' 7656 ) 7657 ) 7658 ) 7659 ) 7660); 7661 7662header('Content-Type: application/xml'); 7663 7664$dom->formatOutput = true; 7665print $dom->saveXML(); 7666?> 7667 ]]> 7668 </programlisting> 7669 getXML's first argument is the SQL query as a string. The second is the $options array. 7670 Let's go through all options step by step: 7671 <itemizedlist> 7672 <listitem> 7673 <emphasis>'rootTag'</emphasis>: we use 'music_library' as the name for the root tag. 7674 </listitem> 7675 <listitem> 7676 <emphasis>'rowTag'</emphasis>: all tags on the first level will represent an artis; therefore we name it 'artis'. 7677 </listitem> 7678 <listitem> 7679 <emphasis>'idColumn'</emphasis>: we set this to 'artistid' because this is the primary key column of the table 7680 artist. At least at this point it should be clear why this option is essential: our LEFT JOIN 7681 returns something like this: 7682 <![CDATA[ 7683artistid name birth_year albumid album_title album_published 7684------------------------------------------------------------------------------- 76851 Curtis Mayfield 1920 1 New World Order 1990 76861 Curtis Mayfield 1920 2 Curtis 1970 76872 Isaac Hayes 1942 3 Shaft 1972 76883 Ray Charles 1930 NULL NULL NULL 7689 ]]> 7690 As we want for every artist only a single tag we need to identify each artist by the primary 7691 key of the table artist. Note that there is a second record for Curtis Mayfield (related to 7692 the album Curtis), but we don't want something like 7693 <programlisting role="tutorial"> 7694 <![CDATA[ 7695<artist> 7696 <name>Curtis Mayfield</name> 7697 <album> 7698 <name>New World Order</name> 7699 </album> 7700</artist> 7701<artist> 7702 <name>Curtis Mayfield</name> 7703 <album> 7704 <name>Curits</name> 7705 </album> 7706</artist> 7707 ]]> 7708 </programlisting> 7709 but rather 7710 <programlisting role="tutorial"> 7711 <![CDATA[ 7712<artist> 7713 <name>Curtis Mayfield</name> 7714 <albums> 7715 <album> 7716 <name>New World Order</name> 7717 </album> 7718 <albums> 7719 <name>Curtis</name> 7720 </albums> 7721 </albums> 7722</artist> 7723 ]]> 7724 </programlisting> 7725 This is achieved by telling XML_Query2XML which entity to focus on (on this level): the artist, as it 7726 is identified by the artist table's primary key. Once XML_Query2XML get's to the second Curtis Mayfield 7727 record, it can tell by the artistid 1 that an XML element was already created for this artist. 7728 </listitem> 7729 <listitem> 7730 <emphasis>'elements'</emphasis>: this is a (not necessarily associative) array of child elements. 7731 <itemizedlist> 7732 <listitem> 7733 <emphasis>'artistid', 'name', 'birth_year', 'birth_place', 'genre'</emphasis>: These are simple element 7734 specifications. The column name will be used as the element name and the element will only 7735 contain the column's value. 7736 </listitem> 7737 <listitem> 7738 <emphasis>'artists'</emphasis>: here we use a complex element specification. It is an array 7739 that can have all the options that can be present on the root level. 7740 <itemizedlist> 7741 <listitem> 7742 <emphasis>'rootTag'</emphasis>: we want all albums to be contained in a singel element named 7743 'albums'. 7744 </listitem> 7745 <listitem> 7746 <emphasis>'rowTag'</emphasis>: each album should be contained in an element named 'album'. 7747 </listitem> 7748 <listitem> 7749 <emphasis>'idColumn'</emphasis>: here we have to use 'albumid' for the ID column as this 7750 is the primary key column for our albums. 7751 </listitem> 7752 <listitem> 7753 <emphasis>'elements'</emphasis>: this time, a simple element specification is all we need: 7754 'albumid', 'title', 'published_year', 'comment' 7755 </listitem> 7756 </itemizedlist> 7757 </listitem> 7758 </itemizedlist> 7759 </listitem> 7760 </itemizedlist> 7761 </para> 7762 </refsect3> 7763 <refsect3 id="{@id case02_xml}"> 7764 <title>case02.xml</title> 7765 <para> 7766 The resulting XML data looks like this: 7767 <programlisting role="tutorial"> 7768 <![CDATA[ 7769<?xml version="1.0" encoding="UTF-8"?> 7770<music_library> 7771 <artist> 7772 <artistid>1</artistid> 7773 <name>Curtis Mayfield</name> 7774 <birth_year>1920</birth_year> 7775 <birth_place>Chicago</birth_place> 7776 <genre>Soul</genre> 7777 <albums> 7778 <album> 7779 <albumid>1</albumid> 7780 <title>New World Order</title> 7781 <published_year>1990</published_year> 7782 <comment>the best ever!</comment> 7783 </album> 7784 <album> 7785 <albumid>2</albumid> 7786 <title>Curtis</title> 7787 <published_year>1970</published_year> 7788 <comment>that man's got somthin' to say</comment> 7789 </album> 7790 </albums> 7791 </artist> 7792 <artist> 7793 <artistid>2</artistid> 7794 <name>Isaac Hayes</name> 7795 <birth_year>1942</birth_year> 7796 <birth_place>Tennessee</birth_place> 7797 <genre>Soul</genre> 7798 <albums> 7799 <album> 7800 <albumid>3</albumid> 7801 <title>Shaft</title> 7802 <published_year>1972</published_year> 7803 <comment>he's the man</comment> 7804 </album> 7805 </albums> 7806 </artist> 7807 <artist> 7808 <artistid>3</artistid> 7809 <name>Ray Charles</name> 7810 <birth_year>1930</birth_year> 7811 <birth_place>Mississippi</birth_place> 7812 <genre>Country and Soul</genre> 7813 <albums /> 7814 </artist> 7815</music_library> 7816 ]]> 7817 </programlisting> 7818 </para> 7819 </refsect3> 7820 <refsect3 id="{@id case02_debug_php}"> 7821 <title>case02_debug.php</title> 7822 <para> 7823 {@link XML_Query2XML::getXML()} allows us to debug and to profile the operation. 7824 <programlisting role="php"> 7825 <![CDATA[ 7826<?php 7827require_once 'XML/Query2XML.php'; 7828require_once 'MDB2.php'; 7829$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 7830 7831require_once 'Log.php'; 7832$debugLogger = Log::factory('file', 'case02.log', 'Query2XML'); 7833$query2xml->enableDebugLog($debugLogger); 7834 7835$query2xml->startProfiling(); 7836 7837 7838$dom = $query2xml->getXML( 7839 "SELECT 7840 * 7841 FROM 7842 artist 7843 LEFT JOIN album ON album.artist_id = artist.artistid", 7844 array( 7845 'rootTag' => 'music_library', 7846 'rowTag' => 'artist', 7847 'idColumn' => 'artistid', 7848 'elements' => array( 7849 'artistid', 7850 'name', 7851 'birth_year', 7852 'birth_place', 7853 'genre', 7854 'albums' => array( 7855 'rootTag' => 'albums', 7856 'rowTag' => 'album', 7857 'idColumn' => 'albumid', 7858 'elements' => array( 7859 'albumid', 7860 'title', 7861 'published_year', 7862 'comment' 7863 ) 7864 ) 7865 ) 7866 ) 7867); 7868 7869header('Content-Type: application/xml'); 7870 7871$dom->formatOutput = true; 7872print $dom->saveXML(); 7873 7874require_once 'File.php'; 7875$fp = new File(); 7876$fp->write('case02.profile', $query2xml->getProfile(), FILE_MODE_WRITE); 7877?> 7878 ]]> 7879 </programlisting> 7880 The lines 5-7 do the debugging, line 9 and 50-52 the profiling. This will create 7881 case02.log and case02.profile. 7882 </para> 7883 </refsect3> 7884 <refsect3 id="{@id case02_log}"> 7885 <title>case02.log</title> 7886 <para> 7887 The format of a debug log file is documented at {@tutorial XML_Query2XML.pkg#debugging}. 7888 Our debug log shows that the query runs once. 7889 <![CDATA[ 7890Feb 11 16:10:36 Query2XML [info] QUERY: SELECT 7891 * 7892 FROM 7893 artist 7894 LEFT JOIN album ON album.artist_id = artist.artistid 7895Feb 11 16:10:36 Query2XML [info] DONE 7896 ]]> 7897 </para> 7898 </refsect3> 7899 <refsect3 id="{@id case02_profile}"> 7900 <title>case02.profile</title> 7901 <para> 7902 Profiling is essential for performance tuning. The format of the output is documented under 7903 {@tutorial XML_Query2XML.pkg#profiling.getprofile}. Our profile looks like this: 7904 <![CDATA[ 7905FROM_DB FROM_CACHE CACHED AVG_DURATION DURATION_SUM SQL 79061 0 false 0.0056409835 0.0056409835 SELECT 7907 * 7908 FROM 7909 artist 7910 LEFT JOIN album ON album.artist_id = artist.artistid 7911 7912TOTAL_DURATION: 0.06843900680542 7913DB_DURATION: 0.015194892883301 7914 ]]> 7915 </para> 7916 </refsect3> 7917 The value "false" in the CACHED column tells us that no caching was performed. As we can see 7918 in the FROM_DB column, the query ran once. 7919 </refsect2> 7920 7921 <refsect2 id="{@id case03}"> 7922 <title>Case 03: Two SELECTs instead of a LEFT OUTER JOIN</title> 7923 <para> 7924 Case 03 will teach you: 7925 <itemizedlist> 7926 <listitem> 7927 How to use {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql}. 7928 </listitem> 7929 <listitem> 7930 Prevent SQL injection. 7931 </listitem> 7932 </itemizedlist> 7933 </para> 7934 <para> 7935 When your query is getting bigger and bigger (say, 6 or more JOINs) you might want to (or have 7936 to, if the maximum number of fields your RDBMS will return has been reached) split up the big join into multiple 7937 smaller joins. Here we will just do exactly the same as in 7938 {@tutorial XML_Query2XML.pkg#casestudies.case02}, but with two separate SELECT queries. 7939 </para> 7940 <refsect3 id="{@id case03_php}"> 7941 <title>case03.php</title> 7942 <para> 7943 case03.php looks like this: 7944 <programlisting role="php"> 7945 <![CDATA[ 7946<?php 7947require_once 'XML/Query2XML.php'; 7948require_once 'MDB2.php'; 7949$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 7950$dom = $query2xml->getXML( 7951 "SELECT 7952 * 7953 FROM 7954 artist", 7955 array( 7956 'rootTag' => 'music_library', 7957 'rowTag' => 'artist', 7958 'idColumn' => 'artistid', 7959 'elements' => array( 7960 'artistid', 7961 'name', 7962 'birth_year', 7963 'birth_place', 7964 'genre', 7965 'albums' => array( 7966 'sql' => array( 7967 'data' => array( 7968 'artistid' 7969 ), 7970 'query' => 'SELECT * FROM album WHERE artist_id = ?' 7971 ), 7972 'rootTag' => 'albums', 7973 'rowTag' => 'album', 7974 'idColumn' => 'albumid', 7975 'elements' => array( 7976 'albumid', 7977 'title', 7978 'published_year', 7979 'comment' 7980 ) 7981 ) 7982 ) 7983 ) 7984); 7985 7986header('Content-Type: application/xml'); 7987 7988$dom->formatOutput = true; 7989print $dom->saveXML(); 7990?> 7991 ]]> 7992 </programlisting> 7993 We won't go over every option as we did for {@tutorial XML_Query2XML.pkg#casestudies.case02.case02_php}. 7994 We will only focus on the differences. The first argument to {@link XML_Query2XML::getXML()} 7995 is a simple SELECT query over one table. What also changed is the complex element 7996 specification of 'albums'. It has a new option: 7997 <itemizedlist> 7998 <listitem> 7999 <emphasis>'sql'</emphasis>: ['sql']['query'] will be executed for every record retrieved with 8000 the first SELECT query. In our case, we want all albums for the current artist record. 8001 We use a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql.complexqueryspecification} here: 8002 ['sql']['data'] contains an array of values that will ultimately be passed to the database abstraction 8003 layer's execute() method. As we do not prefix 'artistid' with anything it is interpreted as a column 8004 name (of the parent record) - which is just what we want. This completely prevents SQL injection attacks. 8005 </listitem> 8006 </itemizedlist> 8007 </para> 8008 </refsect3> 8009 <refsect3 id="{@id case03_xml}"> 8010 <title>case03.xml</title> 8011 <para> 8012 The resulting XML data looks exactly like {@tutorial XML_Query2XML.pkg#casestudies.case02.case02_xml}: 8013 <programlisting role="tutorial"> 8014 <![CDATA[ 8015<?xml version="1.0" encoding="UTF-8"?> 8016<music_library> 8017 <artist> 8018 <artistid>1</artistid> 8019 <name>Curtis Mayfield</name> 8020 <birth_year>1920</birth_year> 8021 <birth_place>Chicago</birth_place> 8022 <genre>Soul</genre> 8023 <albums> 8024 <album> 8025 <albumid>1</albumid> 8026 <title>New World Order</title> 8027 <published_year>1990</published_year> 8028 <comment>the best ever!</comment> 8029 </album> 8030 <album> 8031 <albumid>2</albumid> 8032 <title>Curtis</title> 8033 <published_year>1970</published_year> 8034 <comment>that man's got somthin' to say</comment> 8035 </album> 8036 </albums> 8037 </artist> 8038 <artist> 8039 <artistid>2</artistid> 8040 <name>Isaac Hayes</name> 8041 <birth_year>1942</birth_year> 8042 <birth_place>Tennessee</birth_place> 8043 <genre>Soul</genre> 8044 <albums> 8045 <album> 8046 <albumid>3</albumid> 8047 <title>Shaft</title> 8048 <published_year>1972</published_year> 8049 <comment>he's the man</comment> 8050 </album> 8051 </albums> 8052 </artist> 8053 <artist> 8054 <artistid>3</artistid> 8055 <name>Ray Charles</name> 8056 <birth_year>1930</birth_year> 8057 <birth_place>Mississippi</birth_place> 8058 <genre>Country and Soul</genre> 8059 <albums /> 8060 </artist> 8061</music_library> 8062 ]]> 8063 </programlisting> 8064 </para> 8065 </refsect3> 8066 <refsect3 id="{@id case03_debug_php}"> 8067 <title>case03_debug.php</title> 8068 <para> 8069 case03_debug.php is similar to {@tutorial XML_Query2XML.pkg#casestudies.case02.case02_debug_php}: 8070 <programlisting role="php"> 8071 <![CDATA[ 8072<?php 8073require_once 'XML/Query2XML.php'; 8074require_once 'MDB2.php'; 8075$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 8076 8077require_once 'Log.php'; 8078$debugLogger = Log::factory('file', 'case03.log', 'Query2XML'); 8079$query2xml->enableDebugLog($debugLogger); 8080 8081$query2xml->startProfiling(); 8082 8083 8084$dom = $query2xml->getXML( 8085 "SELECT 8086 * 8087 FROM 8088 artist", 8089 array( 8090 'rootTag' => 'music_library', 8091 'rowTag' => 'artist', 8092 'idColumn' => 'artistid', 8093 'elements' => array( 8094 'artistid', 8095 'name', 8096 'birth_year', 8097 'birth_place', 8098 'genre', 8099 'albums' => array( 8100 'sql' => array( 8101 'data' => array( 8102 'artistid' 8103 ), 8104 'query' => 'SELECT * FROM album WHERE artist_id = ?' 8105 ), 8106 'rootTag' => 'albums', 8107 'rowTag' => 'album', 8108 'idColumn' => 'albumid', 8109 'elements' => array( 8110 'albumid', 8111 'title', 8112 'published_year', 8113 'comment' 8114 ) 8115 ) 8116 ) 8117 ) 8118); 8119 8120header('Content-Type: application/xml'); 8121 8122$dom->formatOutput = true; 8123print $dom->saveXML(); 8124 8125require_once 'File.php'; 8126$fp = new File(); 8127$fp->write('case03.profile', $query2xml->getProfile(), FILE_MODE_WRITE); 8128?> 8129 ]]> 8130 </programlisting> 8131 The lines 6-8 do the debugging, line 10 and 54-56 the profiling. This will create 8132 case03.log and case03.profile. 8133 </para> 8134 </refsect3> 8135 <refsect3 id="{@id case03_log}"> 8136 <title>case03.log</title> 8137 <para> 8138 The format of a debug log file is documented at {@tutorial XML_Query2XML.pkg#debugging}. 8139 Our debug log now contains 4 queries: 8140 <![CDATA[ 8141Apr 18 19:00:20 Query2XML [info] QUERY: SELECT 8142 * 8143 FROM 8144 artist 8145 ORDER BY 8146 artistid 8147Apr 18 19:00:20 Query2XML [info] DONE 8148Apr 18 19:00:20 Query2XML [info] QUERY: SELECT * FROM album WHERE artist_id = ?; DATA:1 8149Apr 18 19:00:20 Query2XML [info] DONE 8150Apr 18 19:00:20 Query2XML [info] QUERY: SELECT * FROM album WHERE artist_id = ?; DATA:2 8151Apr 18 19:00:20 Query2XML [info] DONE 8152Apr 18 19:00:20 Query2XML [info] QUERY: SELECT * FROM album WHERE artist_id = ?; DATA:3 8153Apr 18 19:00:20 Query2XML [info] DONE 8154 ]]> 8155 </para> 8156 The debug log shows what we expected: the first SELECT over the artist table runs once 8157 and the SELECT over the album table runs three times (once for every record found in 8158 the artist table). As the log shows no 'CACHING' entries we also know that no cashing 8159 was performed ({@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options.cached} 8160 was not set to true). 8161 </refsect3> 8162 <refsect3 id="{@id case03_profile}"> 8163 <title>case03.profile</title> 8164 <para> 8165 Profiling is essential for performance tuning. The format of the output is documented under 8166 {@tutorial XML_Query2XML.pkg#profiling.getprofile}. Our profile looks like this: 8167 <![CDATA[ 8168FROM_DB FROM_CACHE CACHED AVG_DURATION DURATION_SUM SQL 81691 0 false 0.0030851364 0.0030851364 SELECT 8170 * 8171 FROM 8172 artist 81733 0 false 0.0035093625 0.0105280876 SELECT * FROM album WHERE artist_id = ? 8174 8175TOTAL_DURATION: 0.090610980987549 8176DB_DURATION: 0.024358034133911 8177 ]]> 8178 If you compare our DB_DURATION value to the one in 8179 {@tutorial XML_Query2XML.pkg#casestudies.case02.case02_profile} you will see that the single LEFT JOIN 8180 was faster than the four separate queries. 8181 </para> 8182 </refsect3> 8183 </refsect2> 8184 <refsect2 id="{@id case04}"> 8185 <title>Case 04: Case 03 with custom tag names, attributes, merge_selective and more</title> 8186 <para> 8187 Case 04 will teach you: 8188 <itemizedlist> 8189 <listitem> 8190 How to use alternative tag names. 8191 </listitem> 8192 <listitem> 8193 How to use callbacks with the '#' prefix. 8194 </listitem> 8195 <listitem> 8196 How to define static node and attribute values using the ':' prefix. 8197 </listitem> 8198 <listitem> 8199 How to prevent the creation of a root tag, using {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_roottag}. 8200 </listitem> 8201 </itemizedlist> 8202 </para> 8203 <para> 8204 This is very much like {@tutorial XML_Query2XML.pkg#casestudies.case03}, but with a demonstration 8205 of some splecial features. 8206 </para> 8207 <para> 8208 In contrast to Case 03 we want: 8209 <itemizedlist> 8210 <listitem> 8211 all tag names should be uppercase 8212 </listitem> 8213 <listitem> 8214 an additional child tag for ARTIST: BIRTH_YEAR_TWO_DIGIT that will contain 8215 only the last two digets of BIRTH_YERAR 8216 </listitem> 8217 <listitem> 8218 the ARTIST tag should have two attributes: ARTISTID and MAINTAINER set to the static value 8219 'Lukas Feiler'. 8220 </listitem> 8221 <listitem> 8222 the ALBUM tags should not be contained in an ALBUMS tag but should be directly within the 8223 ARTIST tag, e.g. 8224 <programlisting role="tutorial"> 8225 <![CDATA[ 8226<artist> 8227 ... 8228 <album>...</album> 8229 <album>...</album> 8230</artist> 8231 ]]> 8232 </programlisting> 8233 instead of 8234 <programlisting role="tutorial"> 8235 <![CDATA[ 8236<artist> 8237 ... 8238 <album> 8239 <album>...</album> 8240 <album>...</album> 8241 </albums> 8242</artist> 8243 ]]> 8244 </programlisting> 8245 </listitem> 8246 <listitem> 8247 the ALBUM tag should have one attribute: ALBUMID 8248 </listitem> 8249 <listitem> 8250 the ALBUM tag should have an additional child tag: GENRE; note that this is a column of 8251 the table artist! 8252 </listitem> 8253 </itemizedlist> 8254 </para> 8255 <refsect3 id="{@id case04_php}"> 8256 <title>case04.php</title> 8257 <para> 8258 case04.php looks like this: 8259 <programlisting role="php"> 8260 <![CDATA[ 8261<?php 8262require_once 'XML/Query2XML.php'; 8263require_once 'MDB2.php'; 8264$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 8265$dom = $query2xml->getXML( 8266 "SELECT 8267 * 8268 FROM 8269 artist", 8270 array( 8271 'rootTag' => 'MUSIC_LIBRARY', 8272 'rowTag' => 'ARTIST', 8273 'idColumn' => 'artistid', 8274 'elements' => array( 8275 'NAME' => 'name', 8276 'BIRTH_YEAR' => 'birth_year', 8277 'BIRTH_YEAR_TWO_DIGIT' => "#firstTwoChars()", 8278 'BIRTH_PLACE' => 'birth_place', 8279 'GENRE' => 'genre', 8280 'albums' => array( 8281 'sql' => array( 8282 'data' => array( 8283 'artistid' 8284 ), 8285 'query' => 'SELECT * FROM album WHERE artist_id = ?' 8286 ), 8287 'sql_options' => array( 8288 'merge_selective' => array('genre') 8289 ), 8290 'rootTag' => '', 8291 'rowTag' => 'ALBUM', 8292 'idColumn' => 'albumid', 8293 'elements' => array( 8294 'TITLE' => 'title', 8295 'PUBLISHED_YEAR' => 'published_year', 8296 'COMMENT' => 'comment', 8297 'GENRE' => 'genre' 8298 ), 8299 'attributes' => array( 8300 'ALBUMID' => 'albumid' 8301 ) 8302 ) 8303 ), 8304 'attributes' => array( 8305 'ARTISTID' => 'artistid', 8306 'MAINTAINER' => ':Lukas Feiler' 8307 ) 8308 ) 8309); 8310 8311header('Content-Type: application/xml'); 8312 8313$dom->formatOutput = true; 8314print $dom->saveXML(); 8315 8316function firstTwoChars($record) 8317{ 8318 return substr($record['birth_year'], 2); 8319} 8320?> 8321 ]]> 8322 </programlisting> 8323 Let's go over the changes: 8324 <itemizedlist> 8325 <listitem> 8326 as we wanted all tag names uppercased, all elements were specified like 8327 <![CDATA[ 8328 'TAG_NAME' => 'column_name' 8329 ]]> 8330 This is because XML_Query2XML will use the array key as the tag name if it is not 8331 numeric. 8332 </listitem> 8333 <listitem> 8334 BIRTH_YEAR_TWO_DIGIT was specified as 8335 <![CDATA[ 8336 'BIRTH_YEAR_TWO_DIGIT' => "#firstTwoChars()", 8337 ]]> 8338 The prefix '#' tells XML_Query2XML that the following string is a function to call. 8339 The current record is passed as argument to that function. firstTwoChars in our case 8340 returns the first two characters of the string stored in $record['birth_year']. 8341 </listitem> 8342 <listitem> 8343 the ARTIST tag now has two attributes: they are specified in an array using 8344 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes the 'attribute' option}. 8345 Both use a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes.simple_attribute_specifications}. 8346 The ARTISTID attribute simply uses the column name 'artistid'. In the MAINTAINER attribute 8347 we specify a static value. This is done by prefixing it by a colon (':'). Without the 8348 colon, XML_Query2XML would treat it as a column name. 8349 </listitem> 8350 <listitem> 8351 the ALBUM tags are now not contained in an ALBUMS tag anymore but directly within the 8352 ARTIST tag; this is done by setting 'rootTag' to an empty string. Alternatively we just could 8353 have omitted the rootTag option. 8354 </listitem> 8355 <listitem> 8356 ALBUM's new attribute ALBUMID is specified using 8357 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes the 'attribute' option}. 8358 </listitem> 8359 <listitem> 8360 ALBUM's new child tag GENRE contains the value of a column of the table artist. 8361 If we had used the sql default options we would have seen a 8362 XML_Query2XML_ConfigException with the following message: 8363 <![CDATA[ 8364[elements][albums][elements][GENRE]: The column "genre" was not found in the result set. 8365 ]]> 8366 This is because the result of the first SQL query is not available at this level. As far as 8367 this level is concerned, it got 8368 overwritten with the result of our second query. But as we need both to be present, we 8369 selectively merger them using {@link http://www.php.net/array_merge array_merge()}. 8370 This is achieved by setting 8371 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options.merge_selective the sql_option 'merge_selective'} 8372 to an array that contains 8373 all columns of the parent record that should also be available on the current level. 8374 As we do not have any confilicting column names, we just leave 8375 {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options.merge_master the sql_option 'merge_master'} 8376 set to false which means that the results of the parent level's query is the 'master', i.e. overwrite 8377 the results from the query on this level. 8378 </listitem> 8379 </itemizedlist> 8380 </para> 8381 </refsect3> 8382 <refsect3 id="{@id case04_xml}"> 8383 <title>case04.xml</title> 8384 <para> 8385 The resulting XML data looks like this: 8386 <programlisting role="tutorial"> 8387 <![CDATA[ 8388<?xml version="1.0" encoding="UTF-8"?> 8389<MUSIC_LIBRARY> 8390 <ARTIST ARTISTID="1" MAINTAINER="Lukas Feiler"> 8391 <NAME>Curtis Mayfield</NAME> 8392 <BIRTH_YEAR>1920</BIRTH_YEAR> 8393 <BIRTH_YEAR_TWO_DIGIT>20</BIRTH_YEAR_TWO_DIGIT> 8394 <BIRTH_PLACE>Chicago</BIRTH_PLACE> 8395 <GENRE>Soul</GENRE> 8396 <ALBUM ALBUMID="1"> 8397 <TITLE>New World Order</TITLE> 8398 <PUBLISHED_YEAR>1990</PUBLISHED_YEAR> 8399 <COMMENT>the best ever!</COMMENT> 8400 <GENRE>Soul</GENRE> 8401 </ALBUM> 8402 <ALBUM ALBUMID="2"> 8403 <TITLE>Curtis</TITLE> 8404 <PUBLISHED_YEAR>1970</PUBLISHED_YEAR> 8405 <COMMENT>that man's got somthin' to say</COMMENT> 8406 <GENRE>Soul</GENRE> 8407 </ALBUM> 8408 </ARTIST> 8409 <ARTIST ARTISTID="2" MAINTAINER="Lukas Feiler"> 8410 <NAME>Isaac Hayes</NAME> 8411 <BIRTH_YEAR>1942</BIRTH_YEAR> 8412 <BIRTH_YEAR_TWO_DIGIT>42</BIRTH_YEAR_TWO_DIGIT> 8413 <BIRTH_PLACE>Tennessee</BIRTH_PLACE> 8414 <GENRE>Soul</GENRE> 8415 <ALBUM ALBUMID="3"> 8416 <TITLE>Shaft</TITLE> 8417 <PUBLISHED_YEAR>1972</PUBLISHED_YEAR> 8418 <COMMENT>he's the man</COMMENT> 8419 <GENRE>Soul</GENRE> 8420 </ALBUM> 8421 </ARTIST> 8422 <ARTIST ARTISTID="3" MAINTAINER="Lukas Feiler"> 8423 <NAME>Ray Charles</NAME> 8424 <BIRTH_YEAR>1930</BIRTH_YEAR> 8425 <BIRTH_YEAR_TWO_DIGIT>30</BIRTH_YEAR_TWO_DIGIT> 8426 <BIRTH_PLACE>Mississippi</BIRTH_PLACE> 8427 <GENRE>Country and Soul</GENRE> 8428 </ARTIST> 8429</MUSIC_LIBRARY> 8430 ]]> 8431 </programlisting> 8432 </para> 8433 </refsect3> 8434 <refsect3 id="{@id case04_debug_php}"> 8435 <title>case04_debug.php</title> 8436 <para> 8437 case04_debug.php reveals nothing new compared to 8438 {@tutorial XML_Query2XML.pkg#casestudies.case03.case03_debug_php} but it's included for 8439 completeness. 8440 <programlisting role="php"> 8441 <![CDATA[ 8442<?php 8443require_once 'XML/Query2XML.php'; 8444require_once 'MDB2.php'; 8445$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 8446 8447require_once 'Log.php'; 8448$debugLogger = Log::factory('file', 'case04.log', 'Query2XML'); 8449$query2xml->enableDebugLog($debugLogger); 8450 8451$query2xml->startProfiling(); 8452 8453 8454$dom = $query2xml->getXML( 8455 "SELECT 8456 * 8457 FROM 8458 artist", 8459 array( 8460 'rootTag' => 'MUSIC_LIBRARY', 8461 'rowTag' => 'ARTIST', 8462 'idColumn' => 'artistid', 8463 'elements' => array( 8464 'NAME' => 'name', 8465 'BIRTH_YEAR' => 'birth_year', 8466 'BIRTH_YEAR_TWO_DIGIT' => "#firstTwoChars()", 8467 'BIRTH_PLACE' => 'birth_place', 8468 'GENRE' => 'genre', 8469 'albums' => array( 8470 'sql' => array( 8471 'data' => array( 8472 'artistid' 8473 ), 8474 'query' => 'SELECT * FROM album WHERE artist_id = ?' 8475 ), 8476 'sql_options' => array( 8477 'merge_selective' => array('genre') 8478 ), 8479 'rootTag' => '', 8480 'rowTag' => 'ALBUM', 8481 'idColumn' => 'albumid', 8482 'elements' => array( 8483 'TITLE' => 'title', 8484 'PUBLISHED_YEAR' => 'published_year', 8485 'COMMENT' => 'comment', 8486 'GENRE' => 'genre' 8487 ), 8488 'attributes' => array( 8489 'ALBUMID' => 'albumid' 8490 ) 8491 ) 8492 ), 8493 'attributes' => array( 8494 'ARTISTID' => 'artistid', 8495 'MAINTAINER' => ':Lukas Feiler' 8496 ) 8497 ) 8498); 8499 8500header('Content-Type: application/xml'); 8501 8502$dom->formatOutput = true; 8503print $dom->saveXML(); 8504 8505require_once 'File.php'; 8506$fp = new File(); 8507$fp->write('case04.profile', $query2xml->getProfile(), FILE_MODE_WRITE); 8508 8509function firstTwoChars($record) 8510{ 8511 return substr($record['birth_year'], 2); 8512} 8513?> 8514 ]]> 8515 </programlisting> 8516 The lines 6-8 do the debugging, line 10 and 64-66 the profiling. This will create 8517 case04.log and case04.profile. 8518 </para> 8519 </refsect3> 8520 <refsect3 id="{@id case04_log}"> 8521 <title>case04.log</title> 8522 <para> 8523 The format of a debug log file is documented at {@tutorial XML_Query2XML.pkg#debugging}. 8524 Our debug log now contains 4 queries and is exactly the same as 8525 {@tutorial XML_Query2XML.pkg#casestudies.case03.case03_log}: 8526 <![CDATA[ 8527Apr 18 19:01:25 Query2XML [info] QUERY: SELECT 8528 * 8529 FROM 8530 artist 8531 ORDER BY 8532 artistid 8533Apr 18 19:01:25 Query2XML [info] DONE 8534Apr 18 19:01:25 Query2XML [info] QUERY: SELECT * FROM album WHERE artist_id = ?; DATA:1 8535Apr 18 19:01:25 Query2XML [info] DONE 8536Apr 18 19:01:25 Query2XML [info] QUERY: SELECT * FROM album WHERE artist_id = ?; DATA:2 8537Apr 18 19:01:25 Query2XML [info] DONE 8538Apr 18 19:01:25 Query2XML [info] QUERY: SELECT * FROM album WHERE artist_id = ?; DATA:3 8539Apr 18 19:01:25 Query2XML [info] DONE 8540 ]]> 8541 </para> 8542 </refsect3> 8543 <refsect3 id="{@id case04_profile}"> 8544 <title>case04.profile</title> 8545 <para> 8546 Profiling is essential for performance tuning. The format of the output is documented under 8547 {@tutorial XML_Query2XML.pkg#profiling.getprofile}. Our profile looks exactly like 8548 {@tutorial XML_Query2XML.pkg#casestudies.case03.case03_profile}: 8549 <![CDATA[ 8550FROM_DB FROM_CACHE CACHED AVG_DURATION DURATION_SUM SQL 85511 0 false 0.0034000873 0.0034000873 SELECT 8552 * 8553 FROM 8554 artist 85553 0 false 0.0035278797 0.0105836391 SELECT * FROM album WHERE artist_id = ? 8556 8557TOTAL_DURATION: 0.081415891647339 8558DB_DURATION: 0.026465892791748 8559 ]]> 8560 </para> 8561 </refsect3> 8562 </refsect2> 8563 <refsect2 id="{@id case05}"> 8564 <title>Case 05: three LEFT OUTER JOINs</title> 8565 <para> 8566 Case 05 will teach you: 8567 <itemizedlist> 8568 <listitem> 8569 How to write {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.complex_element_specifications} 8570 that in turn contain {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.complex_element_specifications}. 8571 </listitem> 8572 <listitem> 8573 How to use static methods for callbacks with the '#' prefix. 8574 </listitem> 8575 <listitem> 8576 How to modify the {@link http://www.php.net/manual/en/ref.dom.php#dom.class.domdocument DOMDocument} 8577 instance returned by getXML(). 8578 </listitem> 8579 </itemizedlist> 8580 </para> 8581 <para> 8582 Case 05 is a demonstration of complex element specifications. 8583 </para> 8584 <refsect3 id="{@id case05_php}"> 8585 <title>case05.php</title> 8586 <para> 8587 case05.php looks like this: 8588 <programlisting role="php"> 8589 <![CDATA[ 8590<?php 8591require_once 'XML/Query2XML.php'; 8592require_once 'MDB2.php'; 8593$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 8594$dom = $query2xml->getXML( 8595 "SELECT 8596 * 8597 FROM 8598 customer c 8599 LEFT JOIN sale s ON c.customerid = s.customer_id 8600 LEFT JOIN album al ON s.album_id = al.albumid 8601 LEFT JOIN artist ar ON al.artist_id = ar.artistid", 8602 array( 8603 'rootTag' => 'music_store', 8604 'rowTag' => 'customer', 8605 'idColumn' => 'customerid', 8606 'elements' => array( 8607 'customerid', 8608 'first_name', 8609 'last_name', 8610 'email', 8611 'sales' => array( 8612 'rootTag' => 'sales', 8613 'rowTag' => 'sale', 8614 'idColumn' => 'saleid', 8615 'elements' => array( 8616 'saleid', 8617 'timestamp', 8618 'date' => '#Callbacks::getFirstWord()', 8619 'time' => '#Callbacks::getSecondWord()', 8620 'album' => array( 8621 'rootTag' => '', 8622 'rowTag' => 'album', 8623 'idColumn' => 'albumid', 8624 'elements' => array( 8625 'albumid', 8626 'title', 8627 'published_year', 8628 'comment', 8629 'artist' => array( 8630 'rootTag' => '', 8631 'rowTag' => 'artist', 8632 'idColumn' => 'artistid', 8633 'elements' => array( 8634 'artistid', 8635 'name', 8636 'birth_year', 8637 'birth_place', 8638 'genre' 8639 ) //artist elements 8640 ) //artist array 8641 ) //album elements 8642 ) //album array 8643 ) //sales elements 8644 ) //sales array 8645 ) //root elements 8646 ) //root 8647); //getXML method call 8648 8649$root = $dom->firstChild; 8650$root->setAttribute('date_generated', date("Y-m-d\TH:i:s", 1124801570)); 8651 8652header('Content-Type: application/xml'); 8653 8654$dom->formatOutput = true; 8655print $dom->saveXML(); 8656 8657class Callbacks 8658{ 8659 function getFirstWord($record) 8660 { 8661 return substr($record['timestamp'], 0, strpos($record['timestamp'], ' ')); 8662 } 8663 8664 function getSecondWord($record) 8665 { 8666 return substr($record['timestamp'], strpos($record['timestamp'], ' ') + 1); 8667 } 8668} 8669?> 8670 ]]> 8671 </programlisting> 8672 ['elements']['sales']['elements']['date'] and ['time'] contain portions of the timestamp column. 8673 Also note that a separate call to DOMNode::setAttribute() is used to 8674 set the attribute date_generated in the root element. 8675 </para> 8676 </refsect3> 8677 <refsect3 id="{@id case05_xml}"> 8678 <title>case05.xml</title> 8679 <para> 8680 The resulting XML data looks like this: 8681 <programlisting role="tutorial"> 8682 <![CDATA[ 8683<?xml version="1.0" encoding="UTF-8"?> 8684<music_store date_generated="2005-08-23T14:52:50"> 8685 <customer> 8686 <customerid>1</customerid> 8687 <first_name>Jane</first_name> 8688 <last_name>Doe</last_name> 8689 <email>jane.doe@example.com</email> 8690 <sales> 8691 <sale> 8692 <saleid>1</saleid> 8693 <timestamp>2005-05-25 16:32:00</timestamp> 8694 <date>2005-05-25</date> 8695 <time>16:32:00</time> 8696 <album> 8697 <albumid>1</albumid> 8698 <title>New World Order</title> 8699 <published_year>1990</published_year> 8700 <comment>the best ever!</comment> 8701 <artist> 8702 <artistid>1</artistid> 8703 <name>Curtis Mayfield</name> 8704 <birth_year>1920</birth_year> 8705 <birth_place>Chicago</birth_place> 8706 <genre>Soul</genre> 8707 </artist> 8708 </album> 8709 </sale> 8710 <sale> 8711 <saleid>11</saleid> 8712 <timestamp>2005-05-25 16:23:00</timestamp> 8713 <date>2005-05-25</date> 8714 <time>16:23:00</time> 8715 <album> 8716 <albumid>2</albumid> 8717 <title>Curtis</title> 8718 <published_year>1970</published_year> 8719 <comment>that man's got somthin' to say</comment> 8720 <artist> 8721 <artistid>1</artistid> 8722 <name>Curtis Mayfield</name> 8723 <birth_year>1920</birth_year> 8724 <birth_place>Chicago</birth_place> 8725 <genre>Soul</genre> 8726 </artist> 8727 </album> 8728 </sale> 8729 </sales> 8730 </customer> 8731 <customer> 8732 <customerid>2</customerid> 8733 <first_name>John</first_name> 8734 <last_name>Doe</last_name> 8735 <email>john.doe@example.com</email> 8736 <sales> 8737 <sale> 8738 <saleid>2</saleid> 8739 <timestamp>2005-06-05 12:56:00</timestamp> 8740 <date>2005-06-05</date> 8741 <time>12:56:00</time> 8742 <album> 8743 <albumid>1</albumid> 8744 <title>New World Order</title> 8745 <published_year>1990</published_year> 8746 <comment>the best ever!</comment> 8747 <artist> 8748 <artistid>1</artistid> 8749 <name>Curtis Mayfield</name> 8750 <birth_year>1920</birth_year> 8751 <birth_place>Chicago</birth_place> 8752 <genre>Soul</genre> 8753 </artist> 8754 </album> 8755 </sale> 8756 <sale> 8757 <saleid>16</saleid> 8758 <timestamp>2005-06-05 12:56:12</timestamp> 8759 <date>2005-06-05</date> 8760 <time>12:56:12</time> 8761 <album> 8762 <albumid>3</albumid> 8763 <title>Shaft</title> 8764 <published_year>1972</published_year> 8765 <comment>he's the man</comment> 8766 <artist> 8767 <artistid>2</artistid> 8768 <name>Isaac Hayes</name> 8769 <birth_year>1942</birth_year> 8770 <birth_place>Tennessee</birth_place> 8771 <genre>Soul</genre> 8772 </artist> 8773 </album> 8774 </sale> 8775 </sales> 8776 </customer> 8777 <customer> 8778 <customerid>3</customerid> 8779 <first_name>Susan</first_name> 8780 <last_name>Green</last_name> 8781 <email>susan.green@example.com</email> 8782 <sales> 8783 <sale> 8784 <saleid>3</saleid> 8785 <timestamp>2005-07-10 11:03:00</timestamp> 8786 <date>2005-07-10</date> 8787 <time>11:03:00</time> 8788 <album> 8789 <albumid>1</albumid> 8790 <title>New World Order</title> 8791 <published_year>1990</published_year> 8792 <comment>the best ever!</comment> 8793 <artist> 8794 <artistid>1</artistid> 8795 <name>Curtis Mayfield</name> 8796 <birth_year>1920</birth_year> 8797 <birth_place>Chicago</birth_place> 8798 <genre>Soul</genre> 8799 </artist> 8800 </album> 8801 </sale> 8802 <sale> 8803 <saleid>12</saleid> 8804 <timestamp>2005-07-10 11:56:00</timestamp> 8805 <date>2005-07-10</date> 8806 <time>11:56:00</time> 8807 <album> 8808 <albumid>2</albumid> 8809 <title>Curtis</title> 8810 <published_year>1970</published_year> 8811 <comment>that man's got somthin' to say</comment> 8812 <artist> 8813 <artistid>1</artistid> 8814 <name>Curtis Mayfield</name> 8815 <birth_year>1920</birth_year> 8816 <birth_place>Chicago</birth_place> 8817 <genre>Soul</genre> 8818 </artist> 8819 </album> 8820 </sale> 8821 </sales> 8822 </customer> 8823 <customer> 8824 <customerid>4</customerid> 8825 <first_name>Victoria</first_name> 8826 <last_name>Alt</last_name> 8827 <email>victory.alt@example.com</email> 8828 <sales> 8829 <sale> 8830 <saleid>4</saleid> 8831 <timestamp>2005-07-10 10:03:00</timestamp> 8832 <date>2005-07-10</date> 8833 <time>10:03:00</time> 8834 <album> 8835 <albumid>1</albumid> 8836 <title>New World Order</title> 8837 <published_year>1990</published_year> 8838 <comment>the best ever!</comment> 8839 <artist> 8840 <artistid>1</artistid> 8841 <name>Curtis Mayfield</name> 8842 <birth_year>1920</birth_year> 8843 <birth_place>Chicago</birth_place> 8844 <genre>Soul</genre> 8845 </artist> 8846 </album> 8847 </sale> 8848 <sale> 8849 <saleid>17</saleid> 8850 <timestamp>2005-07-10 10:03:32</timestamp> 8851 <date>2005-07-10</date> 8852 <time>10:03:32</time> 8853 <album> 8854 <albumid>3</albumid> 8855 <title>Shaft</title> 8856 <published_year>1972</published_year> 8857 <comment>he's the man</comment> 8858 <artist> 8859 <artistid>2</artistid> 8860 <name>Isaac Hayes</name> 8861 <birth_year>1942</birth_year> 8862 <birth_place>Tennessee</birth_place> 8863 <genre>Soul</genre> 8864 </artist> 8865 </album> 8866 </sale> 8867 </sales> 8868 </customer> 8869 <customer> 8870 <customerid>5</customerid> 8871 <first_name>Will</first_name> 8872 <last_name>Rippy</last_name> 8873 <email>will.wippy@example.com</email> 8874 <sales> 8875 <sale> 8876 <saleid>5</saleid> 8877 <timestamp>2005-07-10 13:03:00</timestamp> 8878 <date>2005-07-10</date> 8879 <time>13:03:00</time> 8880 <album> 8881 <albumid>1</albumid> 8882 <title>New World Order</title> 8883 <published_year>1990</published_year> 8884 <comment>the best ever!</comment> 8885 <artist> 8886 <artistid>1</artistid> 8887 <name>Curtis Mayfield</name> 8888 <birth_year>1920</birth_year> 8889 <birth_place>Chicago</birth_place> 8890 <genre>Soul</genre> 8891 </artist> 8892 </album> 8893 </sale> 8894 <sale> 8895 <saleid>13</saleid> 8896 <timestamp>2005-07-10 13:12:00</timestamp> 8897 <date>2005-07-10</date> 8898 <time>13:12:00</time> 8899 <album> 8900 <albumid>2</albumid> 8901 <title>Curtis</title> 8902 <published_year>1970</published_year> 8903 <comment>that man's got somthin' to say</comment> 8904 <artist> 8905 <artistid>1</artistid> 8906 <name>Curtis Mayfield</name> 8907 <birth_year>1920</birth_year> 8908 <birth_place>Chicago</birth_place> 8909 <genre>Soul</genre> 8910 </artist> 8911 </album> 8912 </sale> 8913 </sales> 8914 </customer> 8915 <customer> 8916 <customerid>6</customerid> 8917 <first_name>Tim</first_name> 8918 <last_name>Raw</last_name> 8919 <email>tim.raw@example.com</email> 8920 <sales> 8921 <sale> 8922 <saleid>6</saleid> 8923 <timestamp>2005-07-10 14:03:00</timestamp> 8924 <date>2005-07-10</date> 8925 <time>14:03:00</time> 8926 <album> 8927 <albumid>1</albumid> 8928 <title>New World Order</title> 8929 <published_year>1990</published_year> 8930 <comment>the best ever!</comment> 8931 <artist> 8932 <artistid>1</artistid> 8933 <name>Curtis Mayfield</name> 8934 <birth_year>1920</birth_year> 8935 <birth_place>Chicago</birth_place> 8936 <genre>Soul</genre> 8937 </artist> 8938 </album> 8939 </sale> 8940 <sale> 8941 <saleid>18</saleid> 8942 <timestamp>2005-07-10 14:03:52</timestamp> 8943 <date>2005-07-10</date> 8944 <time>14:03:52</time> 8945 <album> 8946 <albumid>3</albumid> 8947 <title>Shaft</title> 8948 <published_year>1972</published_year> 8949 <comment>he's the man</comment> 8950 <artist> 8951 <artistid>2</artistid> 8952 <name>Isaac Hayes</name> 8953 <birth_year>1942</birth_year> 8954 <birth_place>Tennessee</birth_place> 8955 <genre>Soul</genre> 8956 </artist> 8957 </album> 8958 </sale> 8959 </sales> 8960 </customer> 8961 <customer> 8962 <customerid>7</customerid> 8963 <first_name>Nick</first_name> 8964 <last_name>Fallow</last_name> 8965 <email>nick.fallow@example.com</email> 8966 <sales> 8967 <sale> 8968 <saleid>7</saleid> 8969 <timestamp>2005-07-10 15:03:00</timestamp> 8970 <date>2005-07-10</date> 8971 <time>15:03:00</time> 8972 <album> 8973 <albumid>1</albumid> 8974 <title>New World Order</title> 8975 <published_year>1990</published_year> 8976 <comment>the best ever!</comment> 8977 <artist> 8978 <artistid>1</artistid> 8979 <name>Curtis Mayfield</name> 8980 <birth_year>1920</birth_year> 8981 <birth_place>Chicago</birth_place> 8982 <genre>Soul</genre> 8983 </artist> 8984 </album> 8985 </sale> 8986 <sale> 8987 <saleid>14</saleid> 8988 <timestamp>2005-07-10 15:09:00</timestamp> 8989 <date>2005-07-10</date> 8990 <time>15:09:00</time> 8991 <album> 8992 <albumid>2</albumid> 8993 <title>Curtis</title> 8994 <published_year>1970</published_year> 8995 <comment>that man's got somthin' to say</comment> 8996 <artist> 8997 <artistid>1</artistid> 8998 <name>Curtis Mayfield</name> 8999 <birth_year>1920</birth_year> 9000 <birth_place>Chicago</birth_place> 9001 <genre>Soul</genre> 9002 </artist> 9003 </album> 9004 </sale> 9005 </sales> 9006 </customer> 9007 <customer> 9008 <customerid>8</customerid> 9009 <first_name>Ed</first_name> 9010 <last_name>Burton</last_name> 9011 <email>ed.burton@example.com</email> 9012 <sales> 9013 <sale> 9014 <saleid>8</saleid> 9015 <timestamp>2005-07-10 16:03:00</timestamp> 9016 <date>2005-07-10</date> 9017 <time>16:03:00</time> 9018 <album> 9019 <albumid>1</albumid> 9020 <title>New World Order</title> 9021 <published_year>1990</published_year> 9022 <comment>the best ever!</comment> 9023 <artist> 9024 <artistid>1</artistid> 9025 <name>Curtis Mayfield</name> 9026 <birth_year>1920</birth_year> 9027 <birth_place>Chicago</birth_place> 9028 <genre>Soul</genre> 9029 </artist> 9030 </album> 9031 </sale> 9032 <sale> 9033 <saleid>19</saleid> 9034 <timestamp>2005-07-10 16:03:01</timestamp> 9035 <date>2005-07-10</date> 9036 <time>16:03:01</time> 9037 <album> 9038 <albumid>3</albumid> 9039 <title>Shaft</title> 9040 <published_year>1972</published_year> 9041 <comment>he's the man</comment> 9042 <artist> 9043 <artistid>2</artistid> 9044 <name>Isaac Hayes</name> 9045 <birth_year>1942</birth_year> 9046 <birth_place>Tennessee</birth_place> 9047 <genre>Soul</genre> 9048 </artist> 9049 </album> 9050 </sale> 9051 </sales> 9052 </customer> 9053 <customer> 9054 <customerid>9</customerid> 9055 <first_name>Jack</first_name> 9056 <last_name>Woo</last_name> 9057 <email>jack.woo@example.com</email> 9058 <sales> 9059 <sale> 9060 <saleid>9</saleid> 9061 <timestamp>2005-07-10 18:03:00</timestamp> 9062 <date>2005-07-10</date> 9063 <time>18:03:00</time> 9064 <album> 9065 <albumid>1</albumid> 9066 <title>New World Order</title> 9067 <published_year>1990</published_year> 9068 <comment>the best ever!</comment> 9069 <artist> 9070 <artistid>1</artistid> 9071 <name>Curtis Mayfield</name> 9072 <birth_year>1920</birth_year> 9073 <birth_place>Chicago</birth_place> 9074 <genre>Soul</genre> 9075 </artist> 9076 </album> 9077 </sale> 9078 <sale> 9079 <saleid>15</saleid> 9080 <timestamp>2005-07-10 18:49:00</timestamp> 9081 <date>2005-07-10</date> 9082 <time>18:49:00</time> 9083 <album> 9084 <albumid>2</albumid> 9085 <title>Curtis</title> 9086 <published_year>1970</published_year> 9087 <comment>that man's got somthin' to say</comment> 9088 <artist> 9089 <artistid>1</artistid> 9090 <name>Curtis Mayfield</name> 9091 <birth_year>1920</birth_year> 9092 <birth_place>Chicago</birth_place> 9093 <genre>Soul</genre> 9094 </artist> 9095 </album> 9096 </sale> 9097 </sales> 9098 </customer> 9099 <customer> 9100 <customerid>10</customerid> 9101 <first_name>Maria</first_name> 9102 <last_name>Gonzales</last_name> 9103 <email>maria.gonzales@example.com</email> 9104 <sales> 9105 <sale> 9106 <saleid>10</saleid> 9107 <timestamp>2005-07-10 19:03:00</timestamp> 9108 <date>2005-07-10</date> 9109 <time>19:03:00</time> 9110 <album> 9111 <albumid>1</albumid> 9112 <title>New World Order</title> 9113 <published_year>1990</published_year> 9114 <comment>the best ever!</comment> 9115 <artist> 9116 <artistid>1</artistid> 9117 <name>Curtis Mayfield</name> 9118 <birth_year>1920</birth_year> 9119 <birth_place>Chicago</birth_place> 9120 <genre>Soul</genre> 9121 </artist> 9122 </album> 9123 </sale> 9124 <sale> 9125 <saleid>20</saleid> 9126 <timestamp>2005-07-10 19:03:50</timestamp> 9127 <date>2005-07-10</date> 9128 <time>19:03:50</time> 9129 <album> 9130 <albumid>3</albumid> 9131 <title>Shaft</title> 9132 <published_year>1972</published_year> 9133 <comment>he's the man</comment> 9134 <artist> 9135 <artistid>2</artistid> 9136 <name>Isaac Hayes</name> 9137 <birth_year>1942</birth_year> 9138 <birth_place>Tennessee</birth_place> 9139 <genre>Soul</genre> 9140 </artist> 9141 </album> 9142 </sale> 9143 </sales> 9144 </customer> 9145</music_store> 9146 ]]> 9147 </programlisting> 9148 </para> 9149 </refsect3> 9150 <refsect3 id="{@id case05_debug_php}"> 9151 <title>case05_debug.php</title> 9152 <para> 9153 case05_debug.php: 9154 <programlisting role="php"> 9155 <![CDATA[ 9156<?php 9157require_once 'XML/Query2XML.php'; 9158require_once 'MDB2.php'; 9159$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 9160 9161require_once 'Log.php'; 9162$debugLogger = Log::factory('file', 'case05.log', 'Query2XML'); 9163$query2xml->enableDebugLog($debugLogger); 9164 9165$query2xml->startProfiling(); 9166 9167 9168$dom = $query2xml->getXML( 9169 "SELECT 9170 * 9171 FROM 9172 customer c 9173 LEFT JOIN sale s ON c.customerid = s.customer_id 9174 LEFT JOIN album al ON s.album_id = al.albumid 9175 LEFT JOIN artist ar ON al.artist_id = ar.artistid", 9176 array( 9177 'rootTag' => 'music_store', 9178 'rowTag' => 'customer', 9179 'idColumn' => 'customerid', 9180 'elements' => array( 9181 'customerid', 9182 'first_name', 9183 'last_name', 9184 'email', 9185 'sales' => array( 9186 'rootTag' => 'sales', 9187 'rowTag' => 'sale', 9188 'idColumn' => 'saleid', 9189 'elements' => array( 9190 'saleid', 9191 'timestamp', 9192 'date' => '#Callbacks::getFirstWord()', 9193 'time' => '#Callbacks::getSecondWord()', 9194 'album' => array( 9195 'rootTag' => '', 9196 'rowTag' => 'album', 9197 'idColumn' => 'albumid', 9198 'elements' => array( 9199 'albumid', 9200 'title', 9201 'published_year', 9202 'comment', 9203 'artist' => array( 9204 'rootTag' => '', 9205 'rowTag' => 'artist', 9206 'idColumn' => 'artistid', 9207 'elements' => array( 9208 'artistid', 9209 'name', 9210 'birth_year', 9211 'birth_place', 9212 'genre' 9213 ) //artist elements 9214 ) //artist array 9215 ) //album elements 9216 ) //album array 9217 ) //sales elements 9218 ) //sales array 9219 ) //root elements 9220 ) //root 9221); //getXML method call 9222 9223$root = $dom->firstChild; 9224$root->setAttribute('date_generated', date("Y-m-d\TH:i:s", 1124801570)); 9225 9226header('Content-Type: application/xml'); 9227 9228$dom->formatOutput = true; 9229print $dom->saveXML(); 9230 9231require_once 'File.php'; 9232$fp = new File(); 9233$fp->write('case05.profile', $query2xml->getProfile(), FILE_MODE_WRITE); 9234 9235class Callbacks 9236{ 9237 function getFirstWord($record) 9238 { 9239 return substr($record['timestamp'], 0, strpos($record['timestamp'], ' ')); 9240 } 9241 9242 function getSecondWord($record) 9243 { 9244 return substr($record['timestamp'], strpos($record['timestamp'], ' ') + 1); 9245 } 9246} 9247?> 9248 ]]> 9249 </programlisting> 9250 The lines 6-8 do the debugging, line 10 and 76-78 the profiling. This will create 9251 case05.log and case05.profile. 9252 </para> 9253 </refsect3> 9254 <refsect3 id="{@id case05_log}"> 9255 <title>case05.log</title> 9256 <para> 9257 The format of a debug log file is documented at {@tutorial XML_Query2XML.pkg#debugging}. 9258 Our debug log now contains a single query: 9259 <![CDATA[ 9260Feb 11 17:27:19 Query2XML [info] QUERY: SELECT 9261 * 9262 FROM 9263 customer c 9264 LEFT JOIN sale s ON c.customerid = s.customer_id 9265 LEFT JOIN album al ON s.album_id = al.albumid 9266 LEFT JOIN artist ar ON al.artist_id = ar.artistid 9267Feb 11 17:27:19 Query2XML [info] DONE 9268 ]]> 9269 </para> 9270 </refsect3> 9271 <refsect3 id="{@id case05_profile}"> 9272 <title>case05.profile</title> 9273 <para> 9274 The format of the output is documented under 9275 {@tutorial XML_Query2XML.pkg#profiling.getprofile}: 9276 <![CDATA[ 9277FROM_DB FROM_CACHE CACHED AVG_DURATION DURATION_SUM SQL 92781 0 false 0.0074028968 0.0074028968 SELECT 9279 * 9280 FROM 9281 customer c 9282 LEFT JOIN sale s ON c.customerid = s.customer_id 9283 LEFT JOIN album al ON s.album_id = al.albumid 9284 LEFT JOIN artist ar ON al.artist_id = ar.artistid 9285 9286TOTAL_DURATION: 0.22688508033752 9287DB_DURATION: 0.050441980361938 9288 ]]> 9289 </para> 9290 </refsect3> 9291 </refsect2> 9292 9293 <refsect2 id="{@id case06}"> 9294 <title>Case 06: BIG join over 10 tables</title> 9295 <para> 9296 Case 06 will teach you: 9297 <itemizedlist> 9298 <listitem> 9299 How to write nested {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.complex_element_specifications}. 9300 </listitem> 9301 <listitem> 9302 How to use {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_idcolumn} when 9303 things get really complex. 9304 </listitem> 9305 <listitem> 9306 How to use COMMAND OBJECTS implementing the {@link XML_Query2XML_Callback} interface. 9307 </listitem> 9308 <listitem> 9309 How to use the CONDITIONAL prefix '?' in combination with the '#' prefix. 9310 </listitem> 9311 </itemizedlist> 9312 </para> 9313 <para> 9314 Case 06 demonstrates how complex things can get :) First have a look at the 9315 {@link http://query2xml.sourceforge.net/docs/Query2XML_Tests.jpg ER diagram}. 9316 It shows a company that sells records. The basic structure of the generated 9317 XML document is as follows: 9318 <itemizedlist> 9319 <listitem> 9320 for each store we want a list of all departments that are located in this store 9321 </listitem> 9322 <listitem> 9323 for each department we want a list of all employees that work in this department 9324 </listitem> 9325 <listitem> 9326 for each employee we want a list of all his sales 9327 </listitem> 9328 <listitem> 9329 for each sale we want to know the customer and the album sold 9330 </listitem> 9331 <listitem> 9332 for each album we want to know the artist that performed the music 9333 </listitem> 9334 </itemizedlist> 9335 </para> 9336 <refsect3 id="{@id case06_php}"> 9337 <title>case06.php</title> 9338 <para> 9339 case06.php looks like this: 9340 <programlisting role="php"> 9341 <![CDATA[ 9342<?php 9343require_once 'XML/Query2XML.php'; 9344require_once 'XML/Query2XML/Callback.php'; 9345require_once 'MDB2.php'; 9346 9347/**Static class that provides validation and parsing methods for 9348* generating XML. 9349* 9350* It is static so that we can easyly call its methods from inside 9351* Query2XML using eval'd code. 9352*/ 9353class Helper 9354{ 9355 /**Associative array of US postal state codes*/ 9356 public static $statePostalCodes = array( 9357 'ALABAMA' => 'AL', 'ALASKA' => 'AK', 'AMERICAN SAMOA' => 'AS', 'ARIZONA' => 'AZ', 'ARKANSAS' => 'AR', 'CALIFORNIA' => 'CA', 9358 'COLORADO' => 'CO', 'CONNECTICUT' => 'CT', 'DELAWARE' => 'DE', 'DISTRICT OF COLUMBIA' => 'DC', 'FEDERATED STATES OF MICRONESIA' => 'FM', 9359 'FLORIDA' => 'FL', 'GEORGIA' => 'GA', 'GUAM' => 'GU', 'HAWAII' => 'HI', 'IDAHO' => 'ID', 'ILLINOIS' => 'IL', 'INDIANA' => 'IN', 9360 'IOWA' => 'IA', 'KANSAS' => 'KS', 'KENTUCKY' => 'KY', 'LOUISIANA' => 'LA', 'MAINE' => 'ME', 'MARSHALL ISLANDS' => 'MH', 'MARYLAND' => 'MD', 9361 'MASSACHUSETTS' => 'MA', 'MICHIGAN' => 'MI', 'MINNESOTA' => 'MN', 'MISSISSIPPI' => 'MS', 'MISSOURI' => 'MO', 'MONTANA' => 'MT', 9362 'NEBRASKA' => 'NE', 'NEVADA' => 'NV', 'NEW HAMPSHIRE' => 'NH', 'NEW JERSEY' => 'NJ', 'NEW JESEY' => 'NJ', 'NEW MEXICO' => 'NM', 'NEW YORK' => 'NY', 9363 'NORTH CAROLINA' => 'NC', 'NORTH DAKOTA' => 'ND', 'NORTHERN MARIANA ISLANDS' => 'MP', 'OHIO' => 'OH', 'OKLAHOMA' => 'OK', 'OREGON' => 'OR', 9364 'PALAU' => 'PW', 'PENNSYLVANIA' => 'PA', 'PUERTO RICO' => 'PR', 'RHODE ISLAND' => 'RI', 'SOUTH CAROLINA' => 'SC', 'SOUTH DAKOTA' => 'SD', 9365 'TENNESSEE' => 'TN', 'TEXAS' => 'TX', 'UTAH' => 'UT', 'VERMONT' => 'VT', 'VIRGIN ISLANDS' => 'VI', 'VIRGINIA' => 'VA', 'WASHINGTON' => 'WA', 9366 'WEST VIRGINIA' => 'WV', 'WISCONSIN' => 'WI', 'WYOMING' => 'WY' 9367 ); 9368 9369 /**Translates a US state name into its two-letter postal code. 9370 * If the translation fails, $state is returned unchanged 9371 * @param $record The record 9372 */ 9373 public static function getStatePostalCode($record) 9374 { 9375 $state = $record["state"]; 9376 $s = str_replace(" ", " ", trim(strtoupper($state))); 9377 if (isset(self::$statePostalCodes[$s])) { 9378 return self::$statePostalCodes[$s]; 9379 } else { 9380 return $state; 9381 } 9382 } 9383 9384 function summarize($str, $limit=50, $appendString=' ...') 9385 { 9386 if (strlen($str) > $limit) { 9387 $str = substr($str, 0, $limit - strlen($appendString)) . $appendString; 9388 } 9389 return $str; 9390 } 9391 9392 function summarizeComment($record, $limit) 9393 { 9394 return self::summarize($record["comment"], $limit); 9395 } 9396} 9397 9398/**Command class that implements the command pattern. 9399* It implements the XML_Query2XML_Callback interface 9400* and therefore has to provide the public non-static 9401* method execute(array $record). 9402*/ 9403class UppercaseColumnCommand implements XML_Query2XML_Callback 9404{ 9405 public function __construct($columnName) 9406 { 9407 $this->_columnName = $columnName; 9408 } 9409 public function execute(array $record) 9410 { 9411 return strtoupper($record[$this->_columnName]); 9412 } 9413} 9414 9415$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 9416$dom = $query2xml->getXML( 9417 "SELECT 9418 s.*, 9419 manager.employeeid AS manager_employeeid, 9420 manager.employeename AS manager_employeename, 9421 d.*, 9422 department_head.employeeid AS department_head_employeeid, 9423 department_head.employeename AS department_head_employeename, 9424 e.*, 9425 sa.*, 9426 c.*, 9427 al.*, 9428 ar.*, 9429 (SELECT COUNT(*) FROM sale WHERE sale.store_id = s.storeid) AS store_sales, 9430 (SELECT 9431 COUNT(*) 9432 FROM 9433 sale, employee, employee_department 9434 WHERE 9435 sale.employee_id = employee.employeeid 9436 AND 9437 employee_department.employee_id = employee.employeeid 9438 AND 9439 employee_department.department_id = d.departmentid 9440 ) AS department_sales, 9441 (SELECT 9442 COUNT(*) 9443 FROM 9444 employee, employee_department, department 9445 WHERE 9446 employee_department.employee_id = employee.employeeid 9447 AND 9448 employee_department.department_id = department.departmentid 9449 AND 9450 department.store_id = s.storeid 9451 ) AS store_employees, 9452 (SELECT 9453 COUNT(*) 9454 FROM 9455 employee, employee_department 9456 WHERE 9457 employee_department.employee_id = employee.employeeid 9458 AND 9459 employee_department.department_id = d.departmentid 9460 ) AS department_employees 9461 FROM 9462 store s 9463 LEFT JOIN employee manager ON s.manager = manager.employeeid 9464 LEFT JOIN department d ON d.store_id = s.storeid 9465 LEFT JOIN employee department_head ON department_head.employeeid = d.department_head 9466 LEFT JOIN employee_department ed ON ed.department_id = d.departmentid 9467 LEFT JOIN employee e ON e.employeeid = ed.employee_id 9468 LEFT JOIN sale sa ON sa.employee_id = e.employeeid 9469 LEFT JOIN customer c ON c.customerid = sa.customer_id 9470 LEFT JOIN album al ON al.albumid = sa.album_id 9471 LEFT JOIN artist ar ON ar.artistid = al.artist_id", 9472 array( 9473 'rootTag' => 'music_company', 9474 'rowTag' => 'store', 9475 'idColumn' => 'storeid', 9476 'attributes' => array( 9477 'storeid' 9478 ), 9479 'elements' => array( 9480 'store_sales', 9481 'store_employees', 9482 'manager' => array( 9483 'idColumn' => 'manager_employeeid', 9484 'attributes' => array( 9485 'manager_employeeid' 9486 ), 9487 'elements' => array( 9488 'manager_employeename' 9489 ) 9490 ), 9491 'address' => array( 9492 'elements' => array( 9493 'country', 9494 'state' => '#Helper::getStatePostalCode()', 9495 'city' => new UppercaseColumnCommand('city'), 9496 'street', 9497 'phone' 9498 ) 9499 ), 9500 'department' => array( 9501 'idColumn' => 'departmentid', 9502 'attributes' => array( 9503 'departmentid' 9504 ), 9505 'elements' => array( 9506 'department_sales', 9507 'department_employees', 9508 'departmentname', 9509 'department_head' => array( 9510 'idColumn' => 'department_head_employeeid', 9511 'attributes' => array( 9512 'department_head_employeeid' 9513 ), 9514 'elements' => array( 9515 'department_head_employeename' 9516 ) 9517 ), 9518 'employees' => array( 9519 'rootTag' => 'employees', 9520 'rowTag' => 'employee', 9521 'idColumn' => 'employeeid', 9522 'attributes' => array( 9523 'employeeid' 9524 ), 9525 'elements' => array( 9526 'employeename', 9527 'sales' => array( 9528 'rootTag' => 'sales', 9529 'rowTag' => 'sale', 9530 'idColumn' => 'saleid', 9531 'attributes' => array( 9532 'saleid' 9533 ), 9534 'elements' => array( 9535 'timestamp', 9536 'customer' => array( 9537 'idColumn' => 'customerid', 9538 'attributes' => array( 9539 'customerid' 9540 ), 9541 'elements' => array( 9542 'first_name', 9543 'last_name', 9544 'email' 9545 ) 9546 ), 9547 'album' => array( 9548 'idColumn' => 'albumid', 9549 'attributes' => array( 9550 'albumid' 9551 ), 9552 'elements' => array( 9553 'title', 9554 'published_year', 9555 'comment' => '?#Helper::summarizeComment(12)', 9556 'artist' => array( 9557 'idColumn' => 'artistid', 9558 'attributes' => array( 9559 'artistid' 9560 ), 9561 'elements' => array( 9562 'name', 9563 'birth_year', 9564 'birth_place', 9565 'genre' 9566 ) 9567 ) 9568 ) // album elements 9569 ) //album array 9570 ) //sales elements 9571 ) //sales array 9572 ) //employees elements 9573 ) //employees array 9574 ) //department elements 9575 ) // department array 9576 ) //root elements 9577 ) //root 9578); //getXML method call 9579 9580$root = $dom->firstChild; 9581$root->setAttribute('date_generated', date("Y-m-d\TH:i:s", 1124801570)); 9582 9583header('Content-Type: application/xml'); 9584 9585$dom->formatOutput = true; 9586print $dom->saveXML(); 9587?> 9588 ]]> 9589 </programlisting> 9590 </para> 9591 <para> 9592 Note how {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_idcolumn} is used at the different 9593 levels: 9594 <itemizedlist> 9595 <listitem> 9596 on the root level we want a record for each store and therefore use the primary key of the table 'store' as 9597 the idColumn: 9598 <programlisting role="php"> 9599 <![CDATA[ 9600'idColumn' => 'storeid', 9601 ]]> 9602 </programlisting> 9603 </listitem> 9604 <listitem> 9605 [elements][address] does not use {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_idcolumn} 9606 at all because all columns used at this level ('country', 9607 'state', 'city', 'street' and 'phone') are all columns of the table 'store'. 9608 </listitem> 9609 <listitem> 9610 on the level [elements][department] we want a record for each department (within the current store) 9611 and therefore use the primary key of the table 'department' as the idColumn: 9612 <programlisting role="php"> 9613 <![CDATA[ 9614'idColumn' => 'departmentid', 9615 ]]> 9616 </programlisting> 9617 The reason we can use department.departmentid without listing all departments underneath all stores is of course that 9618 our LEFT JOIN has the condition "LEFT JOIN department ON department.store_id = store.storeid". 9619 </listitem> 9620 <listitem> 9621 on the level [elements][department][elements][employees] we want a record for each employee (within 9622 the current department) and therefore use the primary key of the table 'employee' as the idColumn: 9623 <programlisting role="php"> 9624 <![CDATA[ 9625'idColumn' => 'employeeid', 9626 ]]> 9627 </programlisting> 9628 The reason we can use employee.employeeid without listing all employees underneath all departments is of course that 9629 our LEFT JOIN does the trick for us (implementing a n:n relationship via the table employee_department). 9630 </listitem> 9631 <listitem> 9632 on the level [elements][department][elements][employees][elements][sales] we want a record for each sale 9633 (perfmormed by the current employee) and therefore use the primary key of the table 'sale' as the idColumn: 9634 <programlisting role="php"> 9635 <![CDATA[ 9636'idColumn' => 'saleid', 9637 ]]> 9638 </programlisting> 9639 The reason we can use sale.saleid without listing all sales underneath all employees is of course that 9640 our LEFT JOIN has the condition "LEFT JOIN sale ON sale.employee_id = employee.employeeid". 9641 </listitem> 9642 <listitem> 9643 on the level [elements][department][elements][employees][elements][sales][elements][customer] we want a 9644 record for each customer (which was a party to the current sale) and therefore use the primary key of 9645 the table 'customer' as the idColumn: 9646 <programlisting role="php"> 9647 <![CDATA[ 9648'idColumn' => 'customerid', 9649 ]]> 9650 </programlisting> 9651 Logically speaking we are of course using the foreign key sale.employee_id, but as the equality of 9652 sale.employee_id and employee.employeeid is the condition for our LEFT JOIN, both are the same. 9653 </listitem> 9654 <listitem> 9655 on the level [elements][department][elements][employees][elements][sales][elements][album] we want a 9656 record for each album (which was subject to the current sale) and therefore use the primary key of 9657 the table 'album' as the idColumn: 9658 <programlisting role="php"> 9659 <![CDATA[ 9660'idColumn' => 'albumid', 9661 ]]> 9662 </programlisting> 9663 Logically speaking we are of course using the foreign key sale.album_id, but as the equality of 9664 sale.album_id and album.albumid is the condition for our LEFT JOIN, both are the same. 9665 </listitem> 9666 <listitem> 9667 on the level [elements][department][elements][employees][elements][sales][elements][album][elements][artist] 9668 we want a record for each artist (who permormed the current album) and therefore use the primary key of 9669 the table 'artist' as the idColumn: 9670 <programlisting role="php"> 9671 <![CDATA[ 9672'idColumn' => 'artistid', 9673 ]]> 9674 </programlisting> 9675 Logically speaking we are of course using the foreign key album.artist_id, but as the equality of 9676 album.artist_id and artist.artistid is the condition for our LEFT JOIN, both are the same. 9677 </listitem> 9678 </itemizedlist> 9679 </para> 9680 <para> 9681 We also use a static class called Helper here. Note how Helper::summarizeComment() is called with a second argument. 9682 The current record is always passed as first argument to the callback function. So we specify the "comment" 9683 element as 9684 <programlisting role="php"> 9685 <![CDATA[ 9686'comment' => '?#Helper::summarizeComment(12)', 9687 ]]> 9688 </programlisting> 9689 which means that the string '12' will be passed as second argument to Helper::summarizeComment(). 9690 The CONDITIONAL prefix ? means that the comment element will only appear if the (summarized) 9691 comment != "". 9692 </para> 9693 <para> 9694 In addition to the CALLBACK FUNCTION we also use a COMMAND OBJECT. In our case it is 9695 an instance of the class UppercaseColumnCommand which implements the {@link XML_Query2XML_Callback} 9696 interface. We pass the column's name as constructor argument, so that UppercaseColumnCommand::execute() 9697 knows which column (i.e. which element of the $record array) to pass to strtoupper(). 9698 </para> 9699 </refsect3> 9700 <refsect3 id="{@id case06_xml}"> 9701 <title>case06.xml</title> 9702 <para> 9703 The resulting XML data looks like this: 9704 <programlisting role="tutorial"> 9705 <![CDATA[ 9706<?xml version="1.0" encoding="UTF-8"?> 9707<music_company date_generated="2005-08-23T14:52:50"> 9708 <store storeid="1"> 9709 <store_sales>10</store_sales> 9710 <store_employees>6</store_employees> 9711 <manager manager_employeeid="1"> 9712 <manager_employeename>Michael Jones</manager_employeename> 9713 </manager> 9714 <address> 9715 <country>US</country> 9716 <state>NY</state> 9717 <city>NEW YORK</city> 9718 <street>Broadway & 72nd Str</street> 9719 <phone>123 456 7890</phone> 9720 </address> 9721 <department departmentid="1"> 9722 <department_sales>10</department_sales> 9723 <department_employees>3</department_employees> 9724 <departmentname>Sales</departmentname> 9725 <department_head department_head_employeeid="1"> 9726 <department_head_employeename>Michael Jones</department_head_employeename> 9727 </department_head> 9728 <employees> 9729 <employee employeeid="1"> 9730 <employeename>Michael Jones</employeename> 9731 <sales> 9732 <sale saleid="1"> 9733 <timestamp>2005-05-25 16:32:00</timestamp> 9734 <customer customerid="1"> 9735 <first_name>Jane</first_name> 9736 <last_name>Doe</last_name> 9737 <email>jane.doe@example.com</email> 9738 </customer> 9739 <album albumid="1"> 9740 <title>New World Order</title> 9741 <published_year>1990</published_year> 9742 <comment>the best ...</comment> 9743 <artist artistid="1"> 9744 <name>Curtis Mayfield</name> 9745 <birth_year>1920</birth_year> 9746 <birth_place>Chicago</birth_place> 9747 <genre>Soul</genre> 9748 </artist> 9749 </album> 9750 </sale> 9751 <sale saleid="7"> 9752 <timestamp>2005-07-10 15:03:00</timestamp> 9753 <customer customerid="7"> 9754 <first_name>Nick</first_name> 9755 <last_name>Fallow</last_name> 9756 <email>nick.fallow@example.com</email> 9757 </customer> 9758 <album albumid="1"> 9759 <title>New World Order</title> 9760 <published_year>1990</published_year> 9761 <comment>the best ...</comment> 9762 <artist artistid="1"> 9763 <name>Curtis Mayfield</name> 9764 <birth_year>1920</birth_year> 9765 <birth_place>Chicago</birth_place> 9766 <genre>Soul</genre> 9767 </artist> 9768 </album> 9769 </sale> 9770 <sale saleid="16"> 9771 <timestamp>2005-06-05 12:56:12</timestamp> 9772 <customer customerid="2"> 9773 <first_name>John</first_name> 9774 <last_name>Doe</last_name> 9775 <email>john.doe@example.com</email> 9776 </customer> 9777 <album albumid="3"> 9778 <title>Shaft</title> 9779 <published_year>1972</published_year> 9780 <comment>he's the man</comment> 9781 <artist artistid="2"> 9782 <name>Isaac Hayes</name> 9783 <birth_year>1942</birth_year> 9784 <birth_place>Tennessee</birth_place> 9785 <genre>Soul</genre> 9786 </artist> 9787 </album> 9788 </sale> 9789 <sale saleid="19"> 9790 <timestamp>2005-07-10 16:03:01</timestamp> 9791 <customer customerid="8"> 9792 <first_name>Ed</first_name> 9793 <last_name>Burton</last_name> 9794 <email>ed.burton@example.com</email> 9795 </customer> 9796 <album albumid="3"> 9797 <title>Shaft</title> 9798 <published_year>1972</published_year> 9799 <comment>he's the man</comment> 9800 <artist artistid="2"> 9801 <name>Isaac Hayes</name> 9802 <birth_year>1942</birth_year> 9803 <birth_place>Tennessee</birth_place> 9804 <genre>Soul</genre> 9805 </artist> 9806 </album> 9807 </sale> 9808 </sales> 9809 </employee> 9810 <employee employeeid="2"> 9811 <employeename>Susi Weintraub</employeename> 9812 <sales> 9813 <sale saleid="3"> 9814 <timestamp>2005-07-10 11:03:00</timestamp> 9815 <customer customerid="3"> 9816 <first_name>Susan</first_name> 9817 <last_name>Green</last_name> 9818 <email>susan.green@example.com</email> 9819 </customer> 9820 <album albumid="1"> 9821 <title>New World Order</title> 9822 <published_year>1990</published_year> 9823 <comment>the best ...</comment> 9824 <artist artistid="1"> 9825 <name>Curtis Mayfield</name> 9826 <birth_year>1920</birth_year> 9827 <birth_place>Chicago</birth_place> 9828 <genre>Soul</genre> 9829 </artist> 9830 </album> 9831 </sale> 9832 <sale saleid="9"> 9833 <timestamp>2005-07-10 18:03:00</timestamp> 9834 <customer customerid="9"> 9835 <first_name>Jack</first_name> 9836 <last_name>Woo</last_name> 9837 <email>jack.woo@example.com</email> 9838 </customer> 9839 <album albumid="1"> 9840 <title>New World Order</title> 9841 <published_year>1990</published_year> 9842 <comment>the best ...</comment> 9843 <artist artistid="1"> 9844 <name>Curtis Mayfield</name> 9845 <birth_year>1920</birth_year> 9846 <birth_place>Chicago</birth_place> 9847 <genre>Soul</genre> 9848 </artist> 9849 </album> 9850 </sale> 9851 <sale saleid="17"> 9852 <timestamp>2005-07-10 10:03:32</timestamp> 9853 <customer customerid="4"> 9854 <first_name>Victoria</first_name> 9855 <last_name>Alt</last_name> 9856 <email>victory.alt@example.com</email> 9857 </customer> 9858 <album albumid="3"> 9859 <title>Shaft</title> 9860 <published_year>1972</published_year> 9861 <comment>he's the man</comment> 9862 <artist artistid="2"> 9863 <name>Isaac Hayes</name> 9864 <birth_year>1942</birth_year> 9865 <birth_place>Tennessee</birth_place> 9866 <genre>Soul</genre> 9867 </artist> 9868 </album> 9869 </sale> 9870 <sale saleid="20"> 9871 <timestamp>2005-07-10 19:03:50</timestamp> 9872 <customer customerid="10"> 9873 <first_name>Maria</first_name> 9874 <last_name>Gonzales</last_name> 9875 <email>maria.gonzales@example.com</email> 9876 </customer> 9877 <album albumid="3"> 9878 <title>Shaft</title> 9879 <published_year>1972</published_year> 9880 <comment>he's the man</comment> 9881 <artist artistid="2"> 9882 <name>Isaac Hayes</name> 9883 <birth_year>1942</birth_year> 9884 <birth_place>Tennessee</birth_place> 9885 <genre>Soul</genre> 9886 </artist> 9887 </album> 9888 </sale> 9889 </sales> 9890 </employee> 9891 <employee employeeid="3"> 9892 <employeename>Steve Hack</employeename> 9893 <sales> 9894 <sale saleid="5"> 9895 <timestamp>2005-07-10 13:03:00</timestamp> 9896 <customer customerid="5"> 9897 <first_name>Will</first_name> 9898 <last_name>Rippy</last_name> 9899 <email>will.wippy@example.com</email> 9900 </customer> 9901 <album albumid="1"> 9902 <title>New World Order</title> 9903 <published_year>1990</published_year> 9904 <comment>the best ...</comment> 9905 <artist artistid="1"> 9906 <name>Curtis Mayfield</name> 9907 <birth_year>1920</birth_year> 9908 <birth_place>Chicago</birth_place> 9909 <genre>Soul</genre> 9910 </artist> 9911 </album> 9912 </sale> 9913 <sale saleid="18"> 9914 <timestamp>2005-07-10 14:03:52</timestamp> 9915 <customer customerid="6"> 9916 <first_name>Tim</first_name> 9917 <last_name>Raw</last_name> 9918 <email>tim.raw@example.com</email> 9919 </customer> 9920 <album albumid="3"> 9921 <title>Shaft</title> 9922 <published_year>1972</published_year> 9923 <comment>he's the man</comment> 9924 <artist artistid="2"> 9925 <name>Isaac Hayes</name> 9926 <birth_year>1942</birth_year> 9927 <birth_place>Tennessee</birth_place> 9928 <genre>Soul</genre> 9929 </artist> 9930 </album> 9931 </sale> 9932 </sales> 9933 </employee> 9934 </employees> 9935 </department> 9936 <department departmentid="2"> 9937 <department_sales>0</department_sales> 9938 <department_employees>3</department_employees> 9939 <departmentname>Marketing</departmentname> 9940 <department_head department_head_employeeid="4"> 9941 <department_head_employeename>Joan Kerr</department_head_employeename> 9942 </department_head> 9943 <employees> 9944 <employee employeeid="4"> 9945 <employeename>Joan Kerr</employeename> 9946 <sales/> 9947 </employee> 9948 <employee employeeid="5"> 9949 <employeename>Marcus Roth</employeename> 9950 <sales/> 9951 </employee> 9952 <employee employeeid="6"> 9953 <employeename>Jack Mack</employeename> 9954 <sales/> 9955 </employee> 9956 </employees> 9957 </department> 9958 </store> 9959 <store storeid="2"> 9960 <store_sales>10</store_sales> 9961 <store_employees>6</store_employees> 9962 <manager manager_employeeid="2"> 9963 <manager_employeename>Susi Weintraub</manager_employeename> 9964 </manager> 9965 <address> 9966 <country>US</country> 9967 <state>NY</state> 9968 <city>LARCHMONT</city> 9969 <street>Palmer Ave 71</street> 9970 <phone>456 7890</phone> 9971 </address> 9972 <department departmentid="3"> 9973 <department_sales>10</department_sales> 9974 <department_employees>3</department_employees> 9975 <departmentname>Sales</departmentname> 9976 <department_head department_head_employeeid="7"> 9977 <department_head_employeename>Rita Doktor</department_head_employeename> 9978 </department_head> 9979 <employees> 9980 <employee employeeid="7"> 9981 <employeename>Rita Doktor</employeename> 9982 <sales> 9983 <sale saleid="2"> 9984 <timestamp>2005-06-05 12:56:00</timestamp> 9985 <customer customerid="2"> 9986 <first_name>John</first_name> 9987 <last_name>Doe</last_name> 9988 <email>john.doe@example.com</email> 9989 </customer> 9990 <album albumid="1"> 9991 <title>New World Order</title> 9992 <published_year>1990</published_year> 9993 <comment>the best ...</comment> 9994 <artist artistid="1"> 9995 <name>Curtis Mayfield</name> 9996 <birth_year>1920</birth_year> 9997 <birth_place>Chicago</birth_place> 9998 <genre>Soul</genre> 9999 </artist> 10000 </album> 10001 </sale> 10002 <sale saleid="8"> 10003 <timestamp>2005-07-10 16:03:00</timestamp> 10004 <customer customerid="8"> 10005 <first_name>Ed</first_name> 10006 <last_name>Burton</last_name> 10007 <email>ed.burton@example.com</email> 10008 </customer> 10009 <album albumid="1"> 10010 <title>New World Order</title> 10011 <published_year>1990</published_year> 10012 <comment>the best ...</comment> 10013 <artist artistid="1"> 10014 <name>Curtis Mayfield</name> 10015 <birth_year>1920</birth_year> 10016 <birth_place>Chicago</birth_place> 10017 <genre>Soul</genre> 10018 </artist> 10019 </album> 10020 </sale> 10021 <sale saleid="11"> 10022 <timestamp>2005-05-25 16:23:00</timestamp> 10023 <customer customerid="1"> 10024 <first_name>Jane</first_name> 10025 <last_name>Doe</last_name> 10026 <email>jane.doe@example.com</email> 10027 </customer> 10028 <album albumid="2"> 10029 <title>Curtis</title> 10030 <published_year>1970</published_year> 10031 <comment>that man ...</comment> 10032 <artist artistid="1"> 10033 <name>Curtis Mayfield</name> 10034 <birth_year>1920</birth_year> 10035 <birth_place>Chicago</birth_place> 10036 <genre>Soul</genre> 10037 </artist> 10038 </album> 10039 </sale> 10040 <sale saleid="14"> 10041 <timestamp>2005-07-10 15:09:00</timestamp> 10042 <customer customerid="7"> 10043 <first_name>Nick</first_name> 10044 <last_name>Fallow</last_name> 10045 <email>nick.fallow@example.com</email> 10046 </customer> 10047 <album albumid="2"> 10048 <title>Curtis</title> 10049 <published_year>1970</published_year> 10050 <comment>that man ...</comment> 10051 <artist artistid="1"> 10052 <name>Curtis Mayfield</name> 10053 <birth_year>1920</birth_year> 10054 <birth_place>Chicago</birth_place> 10055 <genre>Soul</genre> 10056 </artist> 10057 </album> 10058 </sale> 10059 </sales> 10060 </employee> 10061 <employee employeeid="8"> 10062 <employeename>David Til</employeename> 10063 <sales> 10064 <sale saleid="4"> 10065 <timestamp>2005-07-10 10:03:00</timestamp> 10066 <customer customerid="4"> 10067 <first_name>Victoria</first_name> 10068 <last_name>Alt</last_name> 10069 <email>victory.alt@example.com</email> 10070 </customer> 10071 <album albumid="1"> 10072 <title>New World Order</title> 10073 <published_year>1990</published_year> 10074 <comment>the best ...</comment> 10075 <artist artistid="1"> 10076 <name>Curtis Mayfield</name> 10077 <birth_year>1920</birth_year> 10078 <birth_place>Chicago</birth_place> 10079 <genre>Soul</genre> 10080 </artist> 10081 </album> 10082 </sale> 10083 <sale saleid="10"> 10084 <timestamp>2005-07-10 19:03:00</timestamp> 10085 <customer customerid="10"> 10086 <first_name>Maria</first_name> 10087 <last_name>Gonzales</last_name> 10088 <email>maria.gonzales@example.com</email> 10089 </customer> 10090 <album albumid="1"> 10091 <title>New World Order</title> 10092 <published_year>1990</published_year> 10093 <comment>the best ...</comment> 10094 <artist artistid="1"> 10095 <name>Curtis Mayfield</name> 10096 <birth_year>1920</birth_year> 10097 <birth_place>Chicago</birth_place> 10098 <genre>Soul</genre> 10099 </artist> 10100 </album> 10101 </sale> 10102 <sale saleid="12"> 10103 <timestamp>2005-07-10 11:56:00</timestamp> 10104 <customer customerid="3"> 10105 <first_name>Susan</first_name> 10106 <last_name>Green</last_name> 10107 <email>susan.green@example.com</email> 10108 </customer> 10109 <album albumid="2"> 10110 <title>Curtis</title> 10111 <published_year>1970</published_year> 10112 <comment>that man ...</comment> 10113 <artist artistid="1"> 10114 <name>Curtis Mayfield</name> 10115 <birth_year>1920</birth_year> 10116 <birth_place>Chicago</birth_place> 10117 <genre>Soul</genre> 10118 </artist> 10119 </album> 10120 </sale> 10121 <sale saleid="15"> 10122 <timestamp>2005-07-10 18:49:00</timestamp> 10123 <customer customerid="9"> 10124 <first_name>Jack</first_name> 10125 <last_name>Woo</last_name> 10126 <email>jack.woo@example.com</email> 10127 </customer> 10128 <album albumid="2"> 10129 <title>Curtis</title> 10130 <published_year>1970</published_year> 10131 <comment>that man ...</comment> 10132 <artist artistid="1"> 10133 <name>Curtis Mayfield</name> 10134 <birth_year>1920</birth_year> 10135 <birth_place>Chicago</birth_place> 10136 <genre>Soul</genre> 10137 </artist> 10138 </album> 10139 </sale> 10140 </sales> 10141 </employee> 10142 <employee employeeid="9"> 10143 <employeename>Pia Eist</employeename> 10144 <sales> 10145 <sale saleid="6"> 10146 <timestamp>2005-07-10 14:03:00</timestamp> 10147 <customer customerid="6"> 10148 <first_name>Tim</first_name> 10149 <last_name>Raw</last_name> 10150 <email>tim.raw@example.com</email> 10151 </customer> 10152 <album albumid="1"> 10153 <title>New World Order</title> 10154 <published_year>1990</published_year> 10155 <comment>the best ...</comment> 10156 <artist artistid="1"> 10157 <name>Curtis Mayfield</name> 10158 <birth_year>1920</birth_year> 10159 <birth_place>Chicago</birth_place> 10160 <genre>Soul</genre> 10161 </artist> 10162 </album> 10163 </sale> 10164 <sale saleid="13"> 10165 <timestamp>2005-07-10 13:12:00</timestamp> 10166 <customer customerid="5"> 10167 <first_name>Will</first_name> 10168 <last_name>Rippy</last_name> 10169 <email>will.wippy@example.com</email> 10170 </customer> 10171 <album albumid="2"> 10172 <title>Curtis</title> 10173 <published_year>1970</published_year> 10174 <comment>that man ...</comment> 10175 <artist artistid="1"> 10176 <name>Curtis Mayfield</name> 10177 <birth_year>1920</birth_year> 10178 <birth_place>Chicago</birth_place> 10179 <genre>Soul</genre> 10180 </artist> 10181 </album> 10182 </sale> 10183 </sales> 10184 </employee> 10185 </employees> 10186 </department> 10187 <department departmentid="4"> 10188 <department_sales>0</department_sales> 10189 <department_employees>3</department_employees> 10190 <departmentname>Marketing</departmentname> 10191 <department_head department_head_employeeid="10"> 10192 <department_head_employeename>Hanna Poll</department_head_employeename> 10193 </department_head> 10194 <employees> 10195 <employee employeeid="10"> 10196 <employeename>Hanna Poll</employeename> 10197 <sales/> 10198 </employee> 10199 <employee employeeid="11"> 10200 <employeename>Jim Wells</employeename> 10201 <sales/> 10202 </employee> 10203 <employee employeeid="12"> 10204 <employeename>Sandra Wilson</employeename> 10205 <sales/> 10206 </employee> 10207 </employees> 10208 </department> 10209 </store> 10210</music_company> 10211 ]]> 10212 </programlisting> 10213 </para> 10214 </refsect3> 10215 <refsect3 id="{@id case06_debug_php}"> 10216 <title>case06_debug.php</title> 10217 <para> 10218 case06_debug.php: 10219 <programlisting role="php"> 10220 <![CDATA[ 10221<?php 10222require_once 'XML/Query2XML.php'; 10223require_once 'XML/Query2XML/Callback.php'; 10224require_once 'MDB2.php'; 10225 10226/**Static class that provides validation and parsing methods for 10227* generating XML. 10228* 10229* It is static so that we can easyly call its methods from inside 10230* Query2XML using eval'd code. 10231*/ 10232class Helper 10233{ 10234 /**Associative array of US postal state codes*/ 10235 public static $statePostalCodes = array( 10236 'ALABAMA' => 'AL', 'ALASKA' => 'AK', 'AMERICAN SAMOA' => 'AS', 'ARIZONA' => 'AZ', 'ARKANSAS' => 'AR', 'CALIFORNIA' => 'CA', 10237 'COLORADO' => 'CO', 'CONNECTICUT' => 'CT', 'DELAWARE' => 'DE', 'DISTRICT OF COLUMBIA' => 'DC', 'FEDERATED STATES OF MICRONESIA' => 'FM', 10238 'FLORIDA' => 'FL', 'GEORGIA' => 'GA', 'GUAM' => 'GU', 'HAWAII' => 'HI', 'IDAHO' => 'ID', 'ILLINOIS' => 'IL', 'INDIANA' => 'IN', 10239 'IOWA' => 'IA', 'KANSAS' => 'KS', 'KENTUCKY' => 'KY', 'LOUISIANA' => 'LA', 'MAINE' => 'ME', 'MARSHALL ISLANDS' => 'MH', 'MARYLAND' => 'MD', 10240 'MASSACHUSETTS' => 'MA', 'MICHIGAN' => 'MI', 'MINNESOTA' => 'MN', 'MISSISSIPPI' => 'MS', 'MISSOURI' => 'MO', 'MONTANA' => 'MT', 10241 'NEBRASKA' => 'NE', 'NEVADA' => 'NV', 'NEW HAMPSHIRE' => 'NH', 'NEW JERSEY' => 'NJ', 'NEW JESEY' => 'NJ', 'NEW MEXICO' => 'NM', 'NEW YORK' => 'NY', 10242 'NORTH CAROLINA' => 'NC', 'NORTH DAKOTA' => 'ND', 'NORTHERN MARIANA ISLANDS' => 'MP', 'OHIO' => 'OH', 'OKLAHOMA' => 'OK', 'OREGON' => 'OR', 10243 'PALAU' => 'PW', 'PENNSYLVANIA' => 'PA', 'PUERTO RICO' => 'PR', 'RHODE ISLAND' => 'RI', 'SOUTH CAROLINA' => 'SC', 'SOUTH DAKOTA' => 'SD', 10244 'TENNESSEE' => 'TN', 'TEXAS' => 'TX', 'UTAH' => 'UT', 'VERMONT' => 'VT', 'VIRGIN ISLANDS' => 'VI', 'VIRGINIA' => 'VA', 'WASHINGTON' => 'WA', 10245 'WEST VIRGINIA' => 'WV', 'WISCONSIN' => 'WI', 'WYOMING' => 'WY' 10246 ); 10247 10248 /**Translates a US state name into its two-letter postal code. 10249 * If the translation fails, $state is returned unchanged 10250 * @param $record The record 10251 */ 10252 public static function getStatePostalCode($record) 10253 { 10254 $state = $record["state"]; 10255 $s = str_replace(" ", " ", trim(strtoupper($state))); 10256 if (isset(self::$statePostalCodes[$s])) { 10257 return self::$statePostalCodes[$s]; 10258 } else { 10259 return $state; 10260 } 10261 } 10262 10263 function summarize($str, $limit=50, $appendString=' ...') 10264 { 10265 if (strlen($str) > $limit) { 10266 $str = substr($str, 0, $limit - strlen($appendString)) . $appendString; 10267 } 10268 return $str; 10269 } 10270 10271 function summarizeComment($record, $limit) 10272 { 10273 return self::summarize($record["comment"], $limit); 10274 } 10275} 10276 10277/**Command class that implements the command pattern. 10278* It implements the XML_Query2XML_Callback interface 10279* and therefore has to provide the public non-static 10280* method execute(array $record). 10281*/ 10282class UppercaseColumnCommand implements XML_Query2XML_Callback 10283{ 10284 public function __construct($columnName) 10285 { 10286 $this->_columnName = $columnName; 10287 } 10288 public function execute(array $record) 10289 { 10290 return strtoupper($record[$this->_columnName]); 10291 } 10292} 10293 10294$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 10295 10296require_once 'Log.php'; 10297$debugLogger = Log::factory('file', 'case06.log', 'XML_Query2XML'); 10298$query2xml->enableDebugLog($debugLogger); 10299 10300$query2xml->startProfiling(); 10301 10302 10303$dom = $query2xml->getXML( 10304 "SELECT 10305 s.*, 10306 manager.employeeid AS manager_employeeid, 10307 manager.employeename AS manager_employeename, 10308 d.*, 10309 department_head.employeeid AS department_head_employeeid, 10310 department_head.employeename AS department_head_employeename, 10311 e.*, 10312 sa.*, 10313 c.*, 10314 al.*, 10315 ar.*, 10316 (SELECT COUNT(*) FROM sale WHERE sale.store_id = s.storeid) AS store_sales, 10317 (SELECT 10318 COUNT(*) 10319 FROM 10320 sale, employee, employee_department 10321 WHERE 10322 sale.employee_id = employee.employeeid 10323 AND 10324 employee_department.employee_id = employee.employeeid 10325 AND 10326 employee_department.department_id = d.departmentid 10327 ) AS department_sales, 10328 (SELECT 10329 COUNT(*) 10330 FROM 10331 employee, employee_department, department 10332 WHERE 10333 employee_department.employee_id = employee.employeeid 10334 AND 10335 employee_department.department_id = department.departmentid 10336 AND 10337 department.store_id = s.storeid 10338 ) AS store_employees, 10339 (SELECT 10340 COUNT(*) 10341 FROM 10342 employee, employee_department 10343 WHERE 10344 employee_department.employee_id = employee.employeeid 10345 AND 10346 employee_department.department_id = d.departmentid 10347 ) AS department_employees 10348 FROM 10349 store s 10350 LEFT JOIN employee manager ON s.manager = manager.employeeid 10351 LEFT JOIN department d ON d.store_id = s.storeid 10352 LEFT JOIN employee department_head ON department_head.employeeid = d.department_head 10353 LEFT JOIN employee_department ed ON ed.department_id = d.departmentid 10354 LEFT JOIN employee e ON e.employeeid = ed.employee_id 10355 LEFT JOIN sale sa ON sa.employee_id = e.employeeid 10356 LEFT JOIN customer c ON c.customerid = sa.customer_id 10357 LEFT JOIN album al ON al.albumid = sa.album_id 10358 LEFT JOIN artist ar ON ar.artistid = al.artist_id", 10359 array( 10360 'rootTag' => 'music_company', 10361 'rowTag' => 'store', 10362 'idColumn' => 'storeid', 10363 'attributes' => array( 10364 'storeid' 10365 ), 10366 'elements' => array( 10367 'store_sales', 10368 'store_employees', 10369 'manager' => array( 10370 'idColumn' => 'manager_employeeid', 10371 'attributes' => array( 10372 'manager_employeeid' 10373 ), 10374 'elements' => array( 10375 'manager_employeename' 10376 ) 10377 ), 10378 'address' => array( 10379 'elements' => array( 10380 'country', 10381 'state' => '#Helper::getStatePostalCode()', 10382 'city' => new UppercaseColumnCommand('city'), 10383 'street', 10384 'phone' 10385 ) 10386 ), 10387 'department' => array( 10388 'idColumn' => 'departmentid', 10389 'attributes' => array( 10390 'departmentid' 10391 ), 10392 'elements' => array( 10393 'department_sales', 10394 'department_employees', 10395 'departmentname', 10396 'department_head' => array( 10397 'idColumn' => 'department_head_employeeid', 10398 'attributes' => array( 10399 'department_head_employeeid' 10400 ), 10401 'elements' => array( 10402 'department_head_employeename' 10403 ) 10404 ), 10405 'employees' => array( 10406 'rootTag' => 'employees', 10407 'rowTag' => 'employee', 10408 'idColumn' => 'employeeid', 10409 'attributes' => array( 10410 'employeeid' 10411 ), 10412 'elements' => array( 10413 'employeename', 10414 'sales' => array( 10415 'rootTag' => 'sales', 10416 'rowTag' => 'sale', 10417 'idColumn' => 'saleid', 10418 'attributes' => array( 10419 'saleid' 10420 ), 10421 'elements' => array( 10422 'timestamp', 10423 'customer' => array( 10424 'idColumn' => 'customerid', 10425 'attributes' => array( 10426 'customerid' 10427 ), 10428 'elements' => array( 10429 'first_name', 10430 'last_name', 10431 'email' 10432 ) 10433 ), 10434 'album' => array( 10435 'idColumn' => 'albumid', 10436 'attributes' => array( 10437 'albumid' 10438 ), 10439 'elements' => array( 10440 'title', 10441 'published_year', 10442 'comment' => '?#Helper::summarizeComment(12)', 10443 'artist' => array( 10444 'idColumn' => 'artistid', 10445 'attributes' => array( 10446 'artistid' 10447 ), 10448 'elements' => array( 10449 'name', 10450 'birth_year', 10451 'birth_place', 10452 'genre' 10453 ) 10454 ) 10455 ) // album elements 10456 ) //album array 10457 ) //sales elements 10458 ) //sales array 10459 ) //employees elements 10460 ) //employees array 10461 ) //department elements 10462 ) // department array 10463 ) //root elements 10464 ) //root 10465); //getXML method call 10466 10467$root = $dom->firstChild; 10468$root->setAttribute('date_generated', date("Y-m-d\TH:i:s", 1124801570)); 10469 10470header('Content-Type: application/xml'); 10471 10472$dom->formatOutput = true; 10473print $dom->saveXML(); 10474 10475require_once 'File.php'; 10476$fp = new File(); 10477$fp->write('case06.profile', $query2xml->getProfile(), FILE_MODE_WRITE); 10478?> 10479 ]]> 10480 </programlisting> 10481 The lines 75-77 do the debugging, line 79 and 254-256 the profiling. This will create 10482 case06.log and case06.profile. 10483 </para> 10484 </refsect3> 10485 <refsect3 id="{@id case06_log}"> 10486 <title>case06.log</title> 10487 <para> 10488 The format of a debug log file is documented at {@tutorial XML_Query2XML.pkg#debugging}. 10489 Our debug log contains just a single query: 10490 <![CDATA[ 10491Feb 11 17:39:46 XML_Query2XML [info] QUERY: SELECT 10492 s.*, 10493 manager.employeeid AS manager_employeeid, 10494 manager.employeename AS manager_employeename, 10495 d.*, 10496 department_head.employeeid AS department_head_employeeid, 10497 department_head.employeename AS department_head_employeename, 10498 e.*, 10499 sa.*, 10500 c.*, 10501 al.*, 10502 ar.*, 10503 (SELECT COUNT(*) FROM sale WHERE sale.store_id = s.storeid) AS store_sales, 10504 (SELECT 10505 COUNT(*) 10506 FROM 10507 sale, employee, employee_department 10508 WHERE 10509 sale.employee_id = employee.employeeid 10510 AND 10511 employee_department.employee_id = employee.employeeid 10512 AND 10513 employee_department.department_id = d.departmentid 10514 ) AS department_sales, 10515 (SELECT 10516 COUNT(*) 10517 FROM 10518 employee, employee_department, department 10519 WHERE 10520 employee_department.employee_id = employee.employeeid 10521 AND 10522 employee_department.department_id = department.departmentid 10523 AND 10524 department.store_id = s.storeid 10525 ) AS store_employees, 10526 (SELECT 10527 COUNT(*) 10528 FROM 10529 employee, employee_department 10530 WHERE 10531 employee_department.employee_id = employee.employeeid 10532 AND 10533 employee_department.department_id = d.departmentid 10534 ) AS department_employees 10535 FROM 10536 store s 10537 LEFT JOIN employee manager ON s.manager = manager.employeeid 10538 LEFT JOIN department d ON d.store_id = s.storeid 10539 LEFT JOIN employee department_head ON department_head.employeeid = d.department_head 10540 LEFT JOIN employee_department ed ON ed.department_id = d.departmentid 10541 LEFT JOIN employee e ON e.employeeid = ed.employee_id 10542 LEFT JOIN sale sa ON sa.employee_id = e.employeeid 10543 LEFT JOIN customer c ON c.customerid = sa.customer_id 10544 LEFT JOIN album al ON al.albumid = sa.album_id 10545 LEFT JOIN artist ar ON ar.artistid = al.artist_id 10546Feb 11 17:39:46 XML_Query2XML [info] DONE 10547 ]]> 10548 </para> 10549 </refsect3> 10550 <refsect3 id="{@id case06_profile}"> 10551 <title>case06.profile</title> 10552 <para> 10553 The format of the output is documented under 10554 {@tutorial XML_Query2XML.pkg#profiling.getprofile}: 10555 <![CDATA[ 10556FROM_DB FROM_CACHE CACHED AVG_DURATION DURATION_SUM SQL 105571 0 false 0.0172939300 0.0172939300 SELECT 10558 s.*, 10559 manager.employeeid AS manager_employeeid, 10560 manager.employeename AS manager_employeename, 10561 d.*, 10562 department_head.employeeid AS department_head_employeeid, 10563 department_head.employeename AS department_head_employeename, 10564 e.*, 10565 sa.*, 10566 c.*, 10567 al.*, 10568 ar.*, 10569 (SELECT COUNT(*) FROM sale WHERE sale.store_id = s.storeid) AS store_sales, 10570 (SELECT 10571 COUNT(*) 10572 FROM 10573 sale, employee, employee_department 10574 WHERE 10575 sale.employee_id = employee.employeeid 10576 AND 10577 employee_department.employee_id = employee.employeeid 10578 AND 10579 employee_department.department_id = d.departmentid 10580 ) AS department_sales, 10581 (SELECT 10582 COUNT(*) 10583 FROM 10584 employee, employee_department, department 10585 WHERE 10586 employee_department.employee_id = employee.employeeid 10587 AND 10588 employee_department.department_id = department.departmentid 10589 AND 10590 department.store_id = s.storeid 10591 ) AS store_employees, 10592 (SELECT 10593 COUNT(*) 10594 FROM 10595 employee, employee_department 10596 WHERE 10597 employee_department.employee_id = employee.employeeid 10598 AND 10599 employee_department.department_id = d.departmentid 10600 ) AS department_employees 10601 FROM 10602 store s 10603 LEFT JOIN employee manager ON s.manager = manager.employeeid 10604 LEFT JOIN department d ON d.store_id = s.storeid 10605 LEFT JOIN employee department_head ON department_head.employeeid = d.department_head 10606 LEFT JOIN employee_department ed ON ed.department_id = d.departmentid 10607 LEFT JOIN employee e ON e.employeeid = ed.employee_id 10608 LEFT JOIN sale sa ON sa.employee_id = e.employeeid 10609 LEFT JOIN customer c ON c.customerid = sa.customer_id 10610 LEFT JOIN album al ON al.albumid = sa.album_id 10611 LEFT JOIN artist ar ON ar.artistid = al.artist_id 10612 10613TOTAL_DURATION: 0.29014992713928 10614DB_DURATION: 0.1554069519043 10615 ]]> 10616 </para> 10617 </refsect3> 10618 </refsect2> 10619 <refsect2 id="{@id case07}"> 10620 <title>Case 07: Case 03 with Asterisk Shortcuts</title> 10621 <para> 10622 Case 07 will teach you: 10623 <itemizedlist> 10624 <listitem> 10625 How to use the {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.asterisk_shortcut} 10626 </listitem> 10627 </itemizedlist> 10628 </para> 10629 <para> 10630 As documented under {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.asterisk_shortcut} 10631 an asterisk can be used to treat all columns found in the result set the same. 10632 </para> 10633 <refsect3 id="{@id case07_php}"> 10634 <title>case07.php</title> 10635 <para> 10636 case07.php looks like this: 10637 <programlisting role="php"> 10638 <![CDATA[ 10639<?php 10640require_once 'XML/Query2XML.php'; 10641require_once 'MDB2.php'; 10642$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 10643$dom = $query2xml->getXML( 10644 "SELECT 10645 * 10646 FROM 10647 artist", 10648 array( 10649 'rootTag' => 'music_library', 10650 'rowTag' => 'artist', 10651 'idColumn' => 'artistid', 10652 'elements' => array( 10653 '*', 10654 'albums' => array( 10655 'sql' => array( 10656 'data' => array( 10657 'artistid' 10658 ), 10659 'query' => 'SELECT * FROM album WHERE artist_id = ?' 10660 ), 10661 'rootTag' => 'albums', 10662 'rowTag' => 'album', 10663 'idColumn' => 'albumid', 10664 'elements' => array( 10665 '*', 10666 'artist_id' => '?:' 10667 ) 10668 ) 10669 ) 10670 ) 10671); 10672 10673header('Content-Type: application/xml'); 10674 10675$dom->formatOutput = true; 10676print $dom->saveXML(); 10677?> 10678 ]]> 10679 </programlisting> 10680 As in {@tutorial XML_Query2XML.pkg#casestudies.case03} we use two separate 10681 queries for the artist and the album tables. All columns of the artist 10682 table are child elements of the artist tags. The columns of the album 10683 table are all below the album tags - with one exception: the artist_id 10684 column is excluded as it only contains redundant information. 10685 </para> 10686 </refsect3> 10687 <refsect3 id="{@id case07_xml}"> 10688 <title>case07.xml</title> 10689 <para> 10690 The resulting XML data is identical with {@tutorial XML_Query2XML.pkg#casestudies.case03.case03_xml}: 10691 <programlisting role="tutorial"> 10692 <![CDATA[ 10693<?xml version="1.0" encoding="UTF-8"?> 10694<music_library> 10695 <artist> 10696 <artistid>1</artistid> 10697 <name>Curtis Mayfield</name> 10698 <birth_year>1920</birth_year> 10699 <birth_place>Chicago</birth_place> 10700 <genre>Soul</genre> 10701 <albums> 10702 <album> 10703 <albumid>1</albumid> 10704 <title>New World Order</title> 10705 <published_year>1990</published_year> 10706 <comment>the best ever!</comment> 10707 </album> 10708 <album> 10709 <albumid>2</albumid> 10710 <title>Curtis</title> 10711 <published_year>1970</published_year> 10712 <comment>that man's got somthin' to say</comment> 10713 </album> 10714 </albums> 10715 </artist> 10716 <artist> 10717 <artistid>2</artistid> 10718 <name>Isaac Hayes</name> 10719 <birth_year>1942</birth_year> 10720 <birth_place>Tennessee</birth_place> 10721 <genre>Soul</genre> 10722 <albums> 10723 <album> 10724 <albumid>3</albumid> 10725 <title>Shaft</title> 10726 <published_year>1972</published_year> 10727 <comment>he's the man</comment> 10728 </album> 10729 </albums> 10730 </artist> 10731 <artist> 10732 <artistid>3</artistid> 10733 <name>Ray Charles</name> 10734 <birth_year>1930</birth_year> 10735 <birth_place>Mississippi</birth_place> 10736 <genre>Country and Soul</genre> 10737 <albums /> 10738 </artist> 10739</music_library> 10740 ]]> 10741 </programlisting> 10742 </para> 10743 </refsect3> 10744 <refsect3 id="{@id case07_debug_php}"> 10745 <title>case07_debug.php</title> 10746 <para> 10747 case07_debug.php is very similar to {@tutorial XML_Query2XML.pkg#casestudies.case03.case03_debug_php}: 10748 <programlisting role="php"> 10749 <![CDATA[ 10750<?php 10751require_once 'XML/Query2XML.php'; 10752require_once 'MDB2.php'; 10753$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 10754 10755require_once 'Log.php'; 10756$debugLogger = Log::factory('file', 'case07.log', 'Query2XML'); 10757$query2xml->enableDebugLog($debugLogger); 10758 10759$query2xml->startProfiling(); 10760 10761 10762$dom = $query2xml->getXML( 10763 "SELECT 10764 * 10765 FROM 10766 artist", 10767 array( 10768 'rootTag' => 'music_library', 10769 'rowTag' => 'artist', 10770 'idColumn' => 'artistid', 10771 'elements' => array( 10772 '*', 10773 'albums' => array( 10774 'sql' => array( 10775 'data' => array( 10776 'artistid' 10777 ), 10778 'query' => 'SELECT * FROM album WHERE artist_id = ?' 10779 ), 10780 'rootTag' => 'albums', 10781 'rowTag' => 'album', 10782 'idColumn' => 'albumid', 10783 'elements' => array( 10784 '*', 10785 'artist_id' => '?:' 10786 ) 10787 ) 10788 ) 10789 ) 10790); 10791 10792header('Content-Type: application/xml'); 10793 10794$dom->formatOutput = true; 10795print $dom->saveXML(); 10796 10797require_once 'File.php'; 10798$fp = new File(); 10799$fp->write('case07.profile', $query2xml->getProfile(), FILE_MODE_WRITE); 10800?> 10801 ]]> 10802 </programlisting> 10803 The lines 6-8 do the debugging, line 10 and 48-50 the profiling. This will create 10804 case07.log and case07.profile. 10805 </para> 10806 </refsect3> 10807 <refsect3 id="{@id case07_log}"> 10808 <title>case07.log</title> 10809 <para> 10810 The format of a debug log file is documented at {@tutorial XML_Query2XML.pkg#debugging}. 10811 Our debug log now contains 4 queries: 10812 <![CDATA[ 10813Apr 18 19:02:48 Query2XML [info] QUERY: SELECT 10814 * 10815 FROM 10816 artist 10817 ORDER BY 10818 artistid 10819Apr 18 19:02:48 Query2XML [info] DONE 10820Apr 18 19:02:48 Query2XML [info] QUERY: SELECT * FROM album WHERE artist_id = ?; DATA:1 10821Apr 18 19:02:48 Query2XML [info] DONE 10822Apr 18 19:02:48 Query2XML [info] QUERY: SELECT * FROM album WHERE artist_id = ?; DATA:2 10823Apr 18 19:02:48 Query2XML [info] DONE 10824Apr 18 19:02:48 Query2XML [info] QUERY: SELECT * FROM album WHERE artist_id = ?; DATA:3 10825Apr 18 19:02:48 Query2XML [info] DONE 10826 ]]> 10827 </para> 10828 The debug log shows what we expected: the first SELECT over the artist table runs once 10829 and the SELECT over the album table runs three times (once for every record found in 10830 the artist table). As the log shows no 'CACHING' entries we also know that no cashing 10831 was performed ({@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options.cached} 10832 was not set to true). 10833 </refsect3> 10834 <refsect3 id="{@id case07_profile}"> 10835 <title>case07.profile</title> 10836 <para> 10837 Profiling is essential for performance tuning. The format of the output is documented under 10838 {@tutorial XML_Query2XML.pkg#profiling.getprofile}. Our profile looks like this: 10839 <![CDATA[ 10840FROM_DB FROM_CACHE CACHED AVG_DURATION DURATION_SUM SQL 108411 0 false 0.0018889904 0.0018889904 SELECT 10842 * 10843 FROM 10844 artist 108453 0 false 0.0021993319 0.0065979957 SELECT * FROM album WHERE artist_id = ? 10846 10847TOTAL_DURATION: 0.052389144897461 10848DB_DURATION: 0.016173124313354 10849 ]]> 10850 </para> 10851 </refsect3> 10852 </refsect2> 10853 <refsect2 id="{@id case08}"> 10854 <title>Case 08: Case 06 revisited: Making use of Mappers</title> 10855 <para> 10856 Case 08 will teach you: 10857 <itemizedlist> 10858 <listitem> 10859 How to do mapping of SQL identifiers to XML element names in accordenace with 10860 {@link http://www.sqlx.org/SQL-XML-documents/5FCD-14-XML-2004-07.pdf ISO/IEC 9075-14:2005}. 10861 </listitem> 10862 <listitem> 10863 How to write your own mappers. 10864 </listitem> 10865 <listitem> 10866 How to use different mappers at different levels. 10867 </listitem> 10868 </itemizedlist> 10869 </para> 10870 <para> 10871 Make sure you are familiar with {@tutorial XML_Query2XML.pkg#casestudies.case06} 10872 before proceeding. Case 08 is different from Case 06 in the following aspects: 10873 <itemizedlist> 10874 <listitem> 10875 All tags and attributes directly related to columns of the table "store" shall be uppercase; 10876 we'll directly use the PHP function strtoupper() and the 'FUNCTION' syntax for this purpose. 10877 </listitem> 10878 <listitem> 10879 All tags and attributes directly related to columns of the table "department" shall use 10880 a naming convention like "oneTwoThree" instead of "one_two_three"; 10881 we'll use Mappers::departmentMapper() and the 'CLASS::STATIC_METHOD' syntax for this purpose. 10882 </listitem> 10883 <listitem> 10884 All tags and attributes directly related to columns of the table "employee" shall use 10885 a naming convention like "OneTwoThree" instead of "one_two_three"; 10886 we'll use Mappers::employeeMapper() and the array('CLASS', 'STATIC_METHOD') syntax for this purpose. 10887 </listitem> 10888 <listitem> 10889 All tags and attributes directly related to columns of the table "sale" shall use 10890 a naming convention like "ONETWOTHREE" instead of "one_two_three"; 10891 we'll use the non-static Mappers::saleMapper() and the array($instance, 'METHOD') syntax for this purpose. 10892 </listitem> 10893 <listitem> 10894 All tags and attributes directly related to columns of the table "customer" shall use 10895 the column names as they are defined in the database; 10896 we'll set the mapper option to false for this purpose. 10897 </listitem> 10898 <listitem> 10899 All tags and attributes directly related to columns of the table "album" shall use 10900 the ISO 9075-mapper; we'll use XML_Query2XML_ISO9075Mapper::map() and the 'CLASS::STATIC_METHOD' 10901 syntax for this purpose. 10902 </listitem> 10903 <listitem> 10904 All tags and attributes directly related to columns of the table "artist" shall use 10905 a naming convention like "onetwothree" instead of "one_two_three"; 10906 we'll the function mapArtist as the mapper and the 'FUNCTION' syntax. 10907 </listitem> 10908 <listitem> 10909 Due to the fact that SQLite prior to 3.1 does not support 10910 a subselect backreferencing to a field in its parent select 10911 (see {@link http://www.sqlite.org/cvstrac/wiki?p=UnsupportedSql}) Case 08 will not work 10912 with SQLite < 3.1. 10913 </listitem> 10914 </itemizedlist> 10915 This certainly is not a very realistic scenario but it was chosen to demonstrate the 10916 different syntaxes for specifying a mapper function and how to use multiple 10917 mappers. This case also demonstrates that all XML names explicitly specified are 10918 not subject to any special mapping! For example the tag names "music_company" and 10919 "store" are left untouched because they are explicitly specified using the options 10920 "rootTag" and "rowTag". An other case is the tag "manager" which also stays untouched. 10921 This is because the array key ("manager" in this case) of a complex element specification 10922 is used for {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_rowtag the option "rowTag"} 10923 per default. XML_Query2XML just does not pretend to be smart than you, the programmer. If you don't 10924 like the tag name "manager", use another array key for the complex element specification or 10925 use the "rowTag" option within the complex element specification. 10926 </para> 10927 <refsect3 id="{@id case08_php}"> 10928 <title>case08.php</title> 10929 <para> 10930 case08.php looks like this: 10931 <programlisting role="php"> 10932 <![CDATA[ 10933<?php 10934class Mappers 10935{ 10936 public static function departmentMapper($str) 10937 { 10938 //maps 'one_two_three' to 'oneTwoThree' 10939 return preg_replace("/(_)([a-z])/e", "strtoupper('\\2')", $str); 10940 } 10941 10942 public static function employeeMapper($str) 10943 { 10944 //maps 'one_two_three' to 'OneTwoThree' 10945 return ucfirst(preg_replace("/(_)([a-z])/e", "strtoupper('\\2')", $str)); 10946 } 10947 10948 public function saleMapper($str) 10949 { 10950 //maps 'one_two_three' to 'ONETWOTHREE' 10951 return strtoupper(str_replace('_', '', $str)); 10952 } 10953} 10954 10955function mapArtist($str) 10956{ 10957 //maps 'one_two_three' to 'onetwothree' 10958 return strtolower(str_replace('_', '', $str)); 10959} 10960 10961$myMappers = new Mappers(); 10962 10963require_once 'XML/Query2XML.php'; 10964require_once 'XML/Query2XML/ISO9075Mapper.php'; 10965require_once 'MDB2.php'; 10966$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 10967$dom = $query2xml->getXML( 10968 "SELECT 10969 s.*, 10970 manager.employeeid AS manager_employeeid, 10971 manager.employeename AS manager_employeename, 10972 d.*, 10973 department_head.employeeid AS department_head_employeeid, 10974 department_head.employeename AS department_head_employeename, 10975 e.*, 10976 sa.*, 10977 c.*, 10978 al.*, 10979 ar.*, 10980 (SELECT COUNT(*) FROM sale WHERE sale.store_id = s.storeid) AS store_sales, 10981 (SELECT 10982 COUNT(*) 10983 FROM 10984 sale, employee, employee_department 10985 WHERE 10986 sale.employee_id = employee.employeeid 10987 AND 10988 employee_department.employee_id = employee.employeeid 10989 AND 10990 employee_department.department_id = d.departmentid 10991 ) AS department_sales, 10992 (SELECT 10993 COUNT(*) 10994 FROM 10995 employee, employee_department, department 10996 WHERE 10997 employee_department.employee_id = employee.employeeid 10998 AND 10999 employee_department.department_id = department.departmentid 11000 AND 11001 department.store_id = s.storeid 11002 ) AS store_employees, 11003 (SELECT 11004 COUNT(*) 11005 FROM 11006 employee, employee_department 11007 WHERE 11008 employee_department.employee_id = employee.employeeid 11009 AND 11010 employee_department.department_id = d.departmentid 11011 ) AS department_employees 11012 FROM 11013 store s 11014 LEFT JOIN employee manager ON s.manager = manager.employeeid 11015 LEFT JOIN department d ON d.store_id = s.storeid 11016 LEFT JOIN employee department_head ON department_head.employeeid = d.department_head 11017 LEFT JOIN employee_department ed ON ed.department_id = d.departmentid 11018 LEFT JOIN employee e ON e.employeeid = ed.employee_id 11019 LEFT JOIN sale sa ON sa.employee_id = e.employeeid 11020 LEFT JOIN customer c ON c.customerid = sa.customer_id 11021 LEFT JOIN album al ON al.albumid = sa.album_id 11022 LEFT JOIN artist ar ON ar.artistid = al.artist_id", 11023 array( 11024 'rootTag' => 'music_company', 11025 'rowTag' => 'store', 11026 'idColumn' => 'storeid', 11027 'mapper' => 'strtoupper', 11028 'attributes' => array( 11029 'storeid' 11030 ), 11031 'elements' => array( 11032 'store_sales', 11033 'store_employees', 11034 'manager' => array( 11035 'idColumn' => 'manager_employeeid', 11036 'attributes' => array( 11037 'manager_employeeid' 11038 ), 11039 'elements' => array( 11040 'manager_employeename' 11041 ) 11042 ), 11043 'address' => array( 11044 'elements' => array( 11045 'country', 11046 'state' => '#Helper::getStatePostalCode()', 11047 'city', 11048 'street', 11049 'phone' 11050 ) 11051 ), 11052 'department' => array( 11053 'idColumn' => 'departmentid', 11054 'mapper' => 'Mappers::departmentMapper', 11055 'attributes' => array( 11056 'departmentid' 11057 ), 11058 'elements' => array( 11059 'department_sales', 11060 'department_employees', 11061 'departmentname', 11062 'department_head' => array( 11063 'idColumn' => 'department_head_employeeid', 11064 'attributes' => array( 11065 'department_head_employeeid' 11066 ), 11067 'elements' => array( 11068 'department_head_employeename' 11069 ) 11070 ), 11071 'employees' => array( 11072 'rootTag' => 'employees', 11073 'rowTag' => 'employee', 11074 'idColumn' => 'employeeid', 11075 'mapper' => array('Mappers', 'employeeMapper'), 11076 'attributes' => array( 11077 'employeeid' 11078 ), 11079 'elements' => array( 11080 'employeename', 11081 'sales' => array( 11082 'rootTag' => 'sales', 11083 'rowTag' => 'sale', 11084 'idColumn' => 'saleid', 11085 'mapper' => array($myMappers, 'saleMapper'), 11086 'attributes' => array( 11087 'saleid' 11088 ), 11089 'elements' => array( 11090 'timestamp', 11091 'customer' => array( 11092 'idColumn' => 'customerid', 11093 'mapper' => false, 11094 'attributes' => array( 11095 'customerid' 11096 ), 11097 'elements' => array( 11098 'first_name', 11099 'last_name', 11100 'email' 11101 ) 11102 ), 11103 'album' => array( 11104 'idColumn' => 'albumid', 11105 'mapper' => 'XML_Query2XML_ISO9075Mapper::map', 11106 'attributes' => array( 11107 'albumid' 11108 ), 11109 'elements' => array( 11110 'title', 11111 'published_year', 11112 'comment' => '?#Helper::summarizeComment(12)', 11113 'artist' => array( 11114 'idColumn' => 'artistid', 11115 'mapper' => 'mapArtist', 11116 'attributes' => array( 11117 'artistid' 11118 ), 11119 'elements' => array( 11120 'name', 11121 'birth_year', 11122 'birth_place', 11123 'genre' 11124 ) 11125 ) 11126 ) // album elements 11127 ) //album array 11128 ) //sales elements 11129 ) //sales array 11130 ) //employees elements 11131 ) //employees array 11132 ) //department elements 11133 ) // department array 11134 ) //root elements 11135 ) //root 11136); //getXML method call 11137 11138$root = $dom->firstChild; 11139$root->setAttribute('date_generated', date("Y-m-d\TH:i:s", 1124801570)); 11140 11141header('Content-Type: application/xml'); 11142 11143$dom->formatOutput = true; 11144print $dom->saveXML(); 11145 11146 11147 11148/**Static class that provides validation and parsing methods for 11149* generating XML. 11150* 11151* It is static so that we can easyly call its methods from inside 11152* Query2XML using eval'd code. 11153*/ 11154class Helper 11155{ 11156 /**Associative array of US postal state codes*/ 11157 public static $statePostalCodes = array( 11158 'ALABAMA' => 'AL', 'ALASKA' => 'AK', 'AMERICAN SAMOA' => 'AS', 'ARIZONA' => 'AZ', 'ARKANSAS' => 'AR', 'CALIFORNIA' => 'CA', 11159 'COLORADO' => 'CO', 'CONNECTICUT' => 'CT', 'DELAWARE' => 'DE', 'DISTRICT OF COLUMBIA' => 'DC', 'FEDERATED STATES OF MICRONESIA' => 'FM', 11160 'FLORIDA' => 'FL', 'GEORGIA' => 'GA', 'GUAM' => 'GU', 'HAWAII' => 'HI', 'IDAHO' => 'ID', 'ILLINOIS' => 'IL', 'INDIANA' => 'IN', 11161 'IOWA' => 'IA', 'KANSAS' => 'KS', 'KENTUCKY' => 'KY', 'LOUISIANA' => 'LA', 'MAINE' => 'ME', 'MARSHALL ISLANDS' => 'MH', 'MARYLAND' => 'MD', 11162 'MASSACHUSETTS' => 'MA', 'MICHIGAN' => 'MI', 'MINNESOTA' => 'MN', 'MISSISSIPPI' => 'MS', 'MISSOURI' => 'MO', 'MONTANA' => 'MT', 11163 'NEBRASKA' => 'NE', 'NEVADA' => 'NV', 'NEW HAMPSHIRE' => 'NH', 'NEW JERSEY' => 'NJ', 'NEW JESEY' => 'NJ', 'NEW MEXICO' => 'NM', 'NEW YORK' => 'NY', 11164 'NORTH CAROLINA' => 'NC', 'NORTH DAKOTA' => 'ND', 'NORTHERN MARIANA ISLANDS' => 'MP', 'OHIO' => 'OH', 'OKLAHOMA' => 'OK', 'OREGON' => 'OR', 11165 'PALAU' => 'PW', 'PENNSYLVANIA' => 'PA', 'PUERTO RICO' => 'PR', 'RHODE ISLAND' => 'RI', 'SOUTH CAROLINA' => 'SC', 'SOUTH DAKOTA' => 'SD', 11166 'TENNESSEE' => 'TN', 'TEXAS' => 'TX', 'UTAH' => 'UT', 'VERMONT' => 'VT', 'VIRGIN ISLANDS' => 'VI', 'VIRGINIA' => 'VA', 'WASHINGTON' => 'WA', 11167 'WEST VIRGINIA' => 'WV', 'WISCONSIN' => 'WI', 'WYOMING' => 'WY' 11168 ); 11169 11170 /**Translates a US state name into its two-letter postal code. 11171 * If the translation fails, $state is returned unchanged 11172 * @param $record The record 11173 */ 11174 public static function getStatePostalCode($record) 11175 { 11176 $state = $record["state"]; 11177 $s = str_replace(" ", " ", trim(strtoupper($state))); 11178 if (isset(self::$statePostalCodes[$s])) { 11179 return self::$statePostalCodes[$s]; 11180 } else { 11181 return $state; 11182 } 11183 } 11184 11185 function summarize($str, $limit=50, $appendString=' ...') 11186 { 11187 if (strlen($str) > $limit) { 11188 $str = substr($str, 0, $limit - strlen($appendString)) . $appendString; 11189 } 11190 return $str; 11191 } 11192 11193 11194 function summarizeComment($record, $limit) 11195 { 11196 return self::summarize($record["comment"], $limit); 11197 } 11198} 11199?> 11200 ]]> 11201 </programlisting> 11202 </para> 11203 </refsect3> 11204 <refsect3 id="{@id case08_xml}"> 11205 <title>case08.xml</title> 11206 <para> 11207 The resulting XML data looks like this: 11208 <programlisting role="tutorial"> 11209 <![CDATA[ 11210<?xml version="1.0" encoding="UTF-8"?> 11211<music_company date_generated="2005-08-23T14:52:50"> 11212 <store STOREID="1"> 11213 <STORE_SALES>10</STORE_SALES> 11214 <STORE_EMPLOYEES>6</STORE_EMPLOYEES> 11215 <manager MANAGER_EMPLOYEEID="1"> 11216 <MANAGER_EMPLOYEENAME>Michael Jones</MANAGER_EMPLOYEENAME> 11217 </manager> 11218 <address> 11219 <COUNTRY>US</COUNTRY> 11220 <STATE>NY</STATE> 11221 <CITY>New York</CITY> 11222 <STREET>Broadway & 72nd Str</STREET> 11223 <PHONE>123 456 7890</PHONE> 11224 </address> 11225 <department departmentid="1"> 11226 <departmentSales>10</departmentSales> 11227 <departmentEmployees>3</departmentEmployees> 11228 <departmentname>Sales</departmentname> 11229 <department_head departmentHeadEmployeeid="1"> 11230 <departmentHeadEmployeename>Michael Jones</departmentHeadEmployeename> 11231 </department_head> 11232 <employees> 11233 <employee Employeeid="1"> 11234 <Employeename>Michael Jones</Employeename> 11235 <sales> 11236 <sale SALEID="1"> 11237 <TIMESTAMP>2005-05-25 16:32:00</TIMESTAMP> 11238 <customer customerid="1"> 11239 <first_name>Jane</first_name> 11240 <last_name>Doe</last_name> 11241 <email>jane.doe@example.com</email> 11242 </customer> 11243 <album albumid="1"> 11244 <title>New World Order</title> 11245 <published_year>1990</published_year> 11246 <comment>the best ...</comment> 11247 <artist artistid="1"> 11248 <name>Curtis Mayfield</name> 11249 <birthyear>1920</birthyear> 11250 <birthplace>Chicago</birthplace> 11251 <genre>Soul</genre> 11252 </artist> 11253 </album> 11254 </sale> 11255 <sale SALEID="7"> 11256 <TIMESTAMP>2005-07-10 15:03:00</TIMESTAMP> 11257 <customer customerid="7"> 11258 <first_name>Nick</first_name> 11259 <last_name>Fallow</last_name> 11260 <email>nick.fallow@example.com</email> 11261 </customer> 11262 <album albumid="1"> 11263 <title>New World Order</title> 11264 <published_year>1990</published_year> 11265 <comment>the best ...</comment> 11266 <artist artistid="1"> 11267 <name>Curtis Mayfield</name> 11268 <birthyear>1920</birthyear> 11269 <birthplace>Chicago</birthplace> 11270 <genre>Soul</genre> 11271 </artist> 11272 </album> 11273 </sale> 11274 <sale SALEID="16"> 11275 <TIMESTAMP>2005-06-05 12:56:12</TIMESTAMP> 11276 <customer customerid="2"> 11277 <first_name>John</first_name> 11278 <last_name>Doe</last_name> 11279 <email>john.doe@example.com</email> 11280 </customer> 11281 <album albumid="3"> 11282 <title>Shaft</title> 11283 <published_year>1972</published_year> 11284 <comment>he's the man</comment> 11285 <artist artistid="2"> 11286 <name>Isaac Hayes</name> 11287 <birthyear>1942</birthyear> 11288 <birthplace>Tennessee</birthplace> 11289 <genre>Soul</genre> 11290 </artist> 11291 </album> 11292 </sale> 11293 <sale SALEID="19"> 11294 <TIMESTAMP>2005-07-10 16:03:01</TIMESTAMP> 11295 <customer customerid="8"> 11296 <first_name>Ed</first_name> 11297 <last_name>Burton</last_name> 11298 <email>ed.burton@example.com</email> 11299 </customer> 11300 <album albumid="3"> 11301 <title>Shaft</title> 11302 <published_year>1972</published_year> 11303 <comment>he's the man</comment> 11304 <artist artistid="2"> 11305 <name>Isaac Hayes</name> 11306 <birthyear>1942</birthyear> 11307 <birthplace>Tennessee</birthplace> 11308 <genre>Soul</genre> 11309 </artist> 11310 </album> 11311 </sale> 11312 </sales> 11313 </employee> 11314 <employee Employeeid="2"> 11315 <Employeename>Susi Weintraub</Employeename> 11316 <sales> 11317 <sale SALEID="3"> 11318 <TIMESTAMP>2005-07-10 11:03:00</TIMESTAMP> 11319 <customer customerid="3"> 11320 <first_name>Susan</first_name> 11321 <last_name>Green</last_name> 11322 <email>susan.green@example.com</email> 11323 </customer> 11324 <album albumid="1"> 11325 <title>New World Order</title> 11326 <published_year>1990</published_year> 11327 <comment>the best ...</comment> 11328 <artist artistid="1"> 11329 <name>Curtis Mayfield</name> 11330 <birthyear>1920</birthyear> 11331 <birthplace>Chicago</birthplace> 11332 <genre>Soul</genre> 11333 </artist> 11334 </album> 11335 </sale> 11336 <sale SALEID="9"> 11337 <TIMESTAMP>2005-07-10 18:03:00</TIMESTAMP> 11338 <customer customerid="9"> 11339 <first_name>Jack</first_name> 11340 <last_name>Woo</last_name> 11341 <email>jack.woo@example.com</email> 11342 </customer> 11343 <album albumid="1"> 11344 <title>New World Order</title> 11345 <published_year>1990</published_year> 11346 <comment>the best ...</comment> 11347 <artist artistid="1"> 11348 <name>Curtis Mayfield</name> 11349 <birthyear>1920</birthyear> 11350 <birthplace>Chicago</birthplace> 11351 <genre>Soul</genre> 11352 </artist> 11353 </album> 11354 </sale> 11355 <sale SALEID="17"> 11356 <TIMESTAMP>2005-07-10 10:03:32</TIMESTAMP> 11357 <customer customerid="4"> 11358 <first_name>Victoria</first_name> 11359 <last_name>Alt</last_name> 11360 <email>victory.alt@example.com</email> 11361 </customer> 11362 <album albumid="3"> 11363 <title>Shaft</title> 11364 <published_year>1972</published_year> 11365 <comment>he's the man</comment> 11366 <artist artistid="2"> 11367 <name>Isaac Hayes</name> 11368 <birthyear>1942</birthyear> 11369 <birthplace>Tennessee</birthplace> 11370 <genre>Soul</genre> 11371 </artist> 11372 </album> 11373 </sale> 11374 <sale SALEID="20"> 11375 <TIMESTAMP>2005-07-10 19:03:50</TIMESTAMP> 11376 <customer customerid="10"> 11377 <first_name>Maria</first_name> 11378 <last_name>Gonzales</last_name> 11379 <email>maria.gonzales@example.com</email> 11380 </customer> 11381 <album albumid="3"> 11382 <title>Shaft</title> 11383 <published_year>1972</published_year> 11384 <comment>he's the man</comment> 11385 <artist artistid="2"> 11386 <name>Isaac Hayes</name> 11387 <birthyear>1942</birthyear> 11388 <birthplace>Tennessee</birthplace> 11389 <genre>Soul</genre> 11390 </artist> 11391 </album> 11392 </sale> 11393 </sales> 11394 </employee> 11395 <employee Employeeid="3"> 11396 <Employeename>Steve Hack</Employeename> 11397 <sales> 11398 <sale SALEID="5"> 11399 <TIMESTAMP>2005-07-10 13:03:00</TIMESTAMP> 11400 <customer customerid="5"> 11401 <first_name>Will</first_name> 11402 <last_name>Rippy</last_name> 11403 <email>will.wippy@example.com</email> 11404 </customer> 11405 <album albumid="1"> 11406 <title>New World Order</title> 11407 <published_year>1990</published_year> 11408 <comment>the best ...</comment> 11409 <artist artistid="1"> 11410 <name>Curtis Mayfield</name> 11411 <birthyear>1920</birthyear> 11412 <birthplace>Chicago</birthplace> 11413 <genre>Soul</genre> 11414 </artist> 11415 </album> 11416 </sale> 11417 <sale SALEID="18"> 11418 <TIMESTAMP>2005-07-10 14:03:52</TIMESTAMP> 11419 <customer customerid="6"> 11420 <first_name>Tim</first_name> 11421 <last_name>Raw</last_name> 11422 <email>tim.raw@example.com</email> 11423 </customer> 11424 <album albumid="3"> 11425 <title>Shaft</title> 11426 <published_year>1972</published_year> 11427 <comment>he's the man</comment> 11428 <artist artistid="2"> 11429 <name>Isaac Hayes</name> 11430 <birthyear>1942</birthyear> 11431 <birthplace>Tennessee</birthplace> 11432 <genre>Soul</genre> 11433 </artist> 11434 </album> 11435 </sale> 11436 </sales> 11437 </employee> 11438 </employees> 11439 </department> 11440 <department departmentid="2"> 11441 <departmentSales>0</departmentSales> 11442 <departmentEmployees>3</departmentEmployees> 11443 <departmentname>Marketing</departmentname> 11444 <department_head departmentHeadEmployeeid="4"> 11445 <departmentHeadEmployeename>Joan Kerr</departmentHeadEmployeename> 11446 </department_head> 11447 <employees> 11448 <employee Employeeid="4"> 11449 <Employeename>Joan Kerr</Employeename> 11450 <sales /> 11451 </employee> 11452 <employee Employeeid="5"> 11453 <Employeename>Marcus Roth</Employeename> 11454 <sales /> 11455 </employee> 11456 <employee Employeeid="6"> 11457 <Employeename>Jack Mack</Employeename> 11458 <sales /> 11459 </employee> 11460 </employees> 11461 </department> 11462 </store> 11463 <store STOREID="2"> 11464 <STORE_SALES>10</STORE_SALES> 11465 <STORE_EMPLOYEES>6</STORE_EMPLOYEES> 11466 <manager MANAGER_EMPLOYEEID="2"> 11467 <MANAGER_EMPLOYEENAME>Susi Weintraub</MANAGER_EMPLOYEENAME> 11468 </manager> 11469 <address> 11470 <COUNTRY>US</COUNTRY> 11471 <STATE>NY</STATE> 11472 <CITY>Larchmont</CITY> 11473 <STREET>Palmer Ave 71</STREET> 11474 <PHONE>456 7890</PHONE> 11475 </address> 11476 <department departmentid="3"> 11477 <departmentSales>10</departmentSales> 11478 <departmentEmployees>3</departmentEmployees> 11479 <departmentname>Sales</departmentname> 11480 <department_head departmentHeadEmployeeid="7"> 11481 <departmentHeadEmployeename>Rita Doktor</departmentHeadEmployeename> 11482 </department_head> 11483 <employees> 11484 <employee Employeeid="7"> 11485 <Employeename>Rita Doktor</Employeename> 11486 <sales> 11487 <sale SALEID="2"> 11488 <TIMESTAMP>2005-06-05 12:56:00</TIMESTAMP> 11489 <customer customerid="2"> 11490 <first_name>John</first_name> 11491 <last_name>Doe</last_name> 11492 <email>john.doe@example.com</email> 11493 </customer> 11494 <album albumid="1"> 11495 <title>New World Order</title> 11496 <published_year>1990</published_year> 11497 <comment>the best ...</comment> 11498 <artist artistid="1"> 11499 <name>Curtis Mayfield</name> 11500 <birthyear>1920</birthyear> 11501 <birthplace>Chicago</birthplace> 11502 <genre>Soul</genre> 11503 </artist> 11504 </album> 11505 </sale> 11506 <sale SALEID="8"> 11507 <TIMESTAMP>2005-07-10 16:03:00</TIMESTAMP> 11508 <customer customerid="8"> 11509 <first_name>Ed</first_name> 11510 <last_name>Burton</last_name> 11511 <email>ed.burton@example.com</email> 11512 </customer> 11513 <album albumid="1"> 11514 <title>New World Order</title> 11515 <published_year>1990</published_year> 11516 <comment>the best ...</comment> 11517 <artist artistid="1"> 11518 <name>Curtis Mayfield</name> 11519 <birthyear>1920</birthyear> 11520 <birthplace>Chicago</birthplace> 11521 <genre>Soul</genre> 11522 </artist> 11523 </album> 11524 </sale> 11525 <sale SALEID="11"> 11526 <TIMESTAMP>2005-05-25 16:23:00</TIMESTAMP> 11527 <customer customerid="1"> 11528 <first_name>Jane</first_name> 11529 <last_name>Doe</last_name> 11530 <email>jane.doe@example.com</email> 11531 </customer> 11532 <album albumid="2"> 11533 <title>Curtis</title> 11534 <published_year>1970</published_year> 11535 <comment>that man ...</comment> 11536 <artist artistid="1"> 11537 <name>Curtis Mayfield</name> 11538 <birthyear>1920</birthyear> 11539 <birthplace>Chicago</birthplace> 11540 <genre>Soul</genre> 11541 </artist> 11542 </album> 11543 </sale> 11544 <sale SALEID="14"> 11545 <TIMESTAMP>2005-07-10 15:09:00</TIMESTAMP> 11546 <customer customerid="7"> 11547 <first_name>Nick</first_name> 11548 <last_name>Fallow</last_name> 11549 <email>nick.fallow@example.com</email> 11550 </customer> 11551 <album albumid="2"> 11552 <title>Curtis</title> 11553 <published_year>1970</published_year> 11554 <comment>that man ...</comment> 11555 <artist artistid="1"> 11556 <name>Curtis Mayfield</name> 11557 <birthyear>1920</birthyear> 11558 <birthplace>Chicago</birthplace> 11559 <genre>Soul</genre> 11560 </artist> 11561 </album> 11562 </sale> 11563 </sales> 11564 </employee> 11565 <employee Employeeid="8"> 11566 <Employeename>David Til</Employeename> 11567 <sales> 11568 <sale SALEID="4"> 11569 <TIMESTAMP>2005-07-10 10:03:00</TIMESTAMP> 11570 <customer customerid="4"> 11571 <first_name>Victoria</first_name> 11572 <last_name>Alt</last_name> 11573 <email>victory.alt@example.com</email> 11574 </customer> 11575 <album albumid="1"> 11576 <title>New World Order</title> 11577 <published_year>1990</published_year> 11578 <comment>the best ...</comment> 11579 <artist artistid="1"> 11580 <name>Curtis Mayfield</name> 11581 <birthyear>1920</birthyear> 11582 <birthplace>Chicago</birthplace> 11583 <genre>Soul</genre> 11584 </artist> 11585 </album> 11586 </sale> 11587 <sale SALEID="10"> 11588 <TIMESTAMP>2005-07-10 19:03:00</TIMESTAMP> 11589 <customer customerid="10"> 11590 <first_name>Maria</first_name> 11591 <last_name>Gonzales</last_name> 11592 <email>maria.gonzales@example.com</email> 11593 </customer> 11594 <album albumid="1"> 11595 <title>New World Order</title> 11596 <published_year>1990</published_year> 11597 <comment>the best ...</comment> 11598 <artist artistid="1"> 11599 <name>Curtis Mayfield</name> 11600 <birthyear>1920</birthyear> 11601 <birthplace>Chicago</birthplace> 11602 <genre>Soul</genre> 11603 </artist> 11604 </album> 11605 </sale> 11606 <sale SALEID="12"> 11607 <TIMESTAMP>2005-07-10 11:56:00</TIMESTAMP> 11608 <customer customerid="3"> 11609 <first_name>Susan</first_name> 11610 <last_name>Green</last_name> 11611 <email>susan.green@example.com</email> 11612 </customer> 11613 <album albumid="2"> 11614 <title>Curtis</title> 11615 <published_year>1970</published_year> 11616 <comment>that man ...</comment> 11617 <artist artistid="1"> 11618 <name>Curtis Mayfield</name> 11619 <birthyear>1920</birthyear> 11620 <birthplace>Chicago</birthplace> 11621 <genre>Soul</genre> 11622 </artist> 11623 </album> 11624 </sale> 11625 <sale SALEID="15"> 11626 <TIMESTAMP>2005-07-10 18:49:00</TIMESTAMP> 11627 <customer customerid="9"> 11628 <first_name>Jack</first_name> 11629 <last_name>Woo</last_name> 11630 <email>jack.woo@example.com</email> 11631 </customer> 11632 <album albumid="2"> 11633 <title>Curtis</title> 11634 <published_year>1970</published_year> 11635 <comment>that man ...</comment> 11636 <artist artistid="1"> 11637 <name>Curtis Mayfield</name> 11638 <birthyear>1920</birthyear> 11639 <birthplace>Chicago</birthplace> 11640 <genre>Soul</genre> 11641 </artist> 11642 </album> 11643 </sale> 11644 </sales> 11645 </employee> 11646 <employee Employeeid="9"> 11647 <Employeename>Pia Eist</Employeename> 11648 <sales> 11649 <sale SALEID="6"> 11650 <TIMESTAMP>2005-07-10 14:03:00</TIMESTAMP> 11651 <customer customerid="6"> 11652 <first_name>Tim</first_name> 11653 <last_name>Raw</last_name> 11654 <email>tim.raw@example.com</email> 11655 </customer> 11656 <album albumid="1"> 11657 <title>New World Order</title> 11658 <published_year>1990</published_year> 11659 <comment>the best ...</comment> 11660 <artist artistid="1"> 11661 <name>Curtis Mayfield</name> 11662 <birthyear>1920</birthyear> 11663 <birthplace>Chicago</birthplace> 11664 <genre>Soul</genre> 11665 </artist> 11666 </album> 11667 </sale> 11668 <sale SALEID="13"> 11669 <TIMESTAMP>2005-07-10 13:12:00</TIMESTAMP> 11670 <customer customerid="5"> 11671 <first_name>Will</first_name> 11672 <last_name>Rippy</last_name> 11673 <email>will.wippy@example.com</email> 11674 </customer> 11675 <album albumid="2"> 11676 <title>Curtis</title> 11677 <published_year>1970</published_year> 11678 <comment>that man ...</comment> 11679 <artist artistid="1"> 11680 <name>Curtis Mayfield</name> 11681 <birthyear>1920</birthyear> 11682 <birthplace>Chicago</birthplace> 11683 <genre>Soul</genre> 11684 </artist> 11685 </album> 11686 </sale> 11687 </sales> 11688 </employee> 11689 </employees> 11690 </department> 11691 <department departmentid="4"> 11692 <departmentSales>0</departmentSales> 11693 <departmentEmployees>3</departmentEmployees> 11694 <departmentname>Marketing</departmentname> 11695 <department_head departmentHeadEmployeeid="10"> 11696 <departmentHeadEmployeename>Hanna Poll</departmentHeadEmployeename> 11697 </department_head> 11698 <employees> 11699 <employee Employeeid="10"> 11700 <Employeename>Hanna Poll</Employeename> 11701 <sales /> 11702 </employee> 11703 <employee Employeeid="11"> 11704 <Employeename>Jim Wells</Employeename> 11705 <sales /> 11706 </employee> 11707 <employee Employeeid="12"> 11708 <Employeename>Sandra Wilson</Employeename> 11709 <sales /> 11710 </employee> 11711 </employees> 11712 </department> 11713 </store> 11714</music_company> 11715 ]]> 11716 </programlisting> 11717 </para> 11718 </refsect3> 11719 <refsect3 id="{@id case08_debug_php}"> 11720 <title>case08_debug.php</title> 11721 <para> 11722 case08_debug.php: 11723 <programlisting role="php"> 11724 <![CDATA[ 11725<?php 11726class Mappers 11727{ 11728 public static function departmentMapper($str) 11729 { 11730 //maps 'one_two_three' to 'oneTwoThree' 11731 return preg_replace("/(_)([a-z])/e", "strtoupper('\\2')", $str); 11732 } 11733 11734 public static function employeeMapper($str) 11735 { 11736 //maps 'one_two_three' to 'OneTwoThree' 11737 return ucfirst(preg_replace("/(_)([a-z])/e", "strtoupper('\\2')", $str)); 11738 } 11739 11740 public function saleMapper($str) 11741 { 11742 //maps 'one_two_three' to 'ONETWOTHREE' 11743 return strtoupper(str_replace('_', '', $str)); 11744 } 11745} 11746 11747function mapArtist($str) 11748{ 11749 //maps 'one_two_three' to 'onetwothree' 11750 return strtolower(str_replace('_', '', $str)); 11751} 11752 11753$myMappers = new Mappers(); 11754 11755require_once 'XML/Query2XML.php'; 11756require_once 'XML/Query2XML/ISO9075Mapper.php'; 11757require_once 'MDB2.php'; 11758$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests')); 11759 11760require_once 'Log.php'; 11761$debugLogger = Log::factory('file', 'case08.log', 'XML_Query2XML'); 11762$query2xml->enableDebugLog($debugLogger); 11763 11764$query2xml->startProfiling(); 11765 11766 11767$dom = $query2xml->getXML( 11768 "SELECT 11769 s.*, 11770 manager.employeeid AS manager_employeeid, 11771 manager.employeename AS manager_employeename, 11772 d.*, 11773 department_head.employeeid AS department_head_employeeid, 11774 department_head.employeename AS department_head_employeename, 11775 e.*, 11776 sa.*, 11777 c.*, 11778 al.*, 11779 ar.*, 11780 (SELECT COUNT(*) FROM sale WHERE sale.store_id = s.storeid) AS store_sales, 11781 (SELECT 11782 COUNT(*) 11783 FROM 11784 sale, employee, employee_department 11785 WHERE 11786 sale.employee_id = employee.employeeid 11787 AND 11788 employee_department.employee_id = employee.employeeid 11789 AND 11790 employee_department.department_id = d.departmentid 11791 ) AS department_sales, 11792 (SELECT 11793 COUNT(*) 11794 FROM 11795 employee, employee_department, department 11796 WHERE 11797 employee_department.employee_id = employee.employeeid 11798 AND 11799 employee_department.department_id = department.departmentid 11800 AND 11801 department.store_id = s.storeid 11802 ) AS store_employees, 11803 (SELECT 11804 COUNT(*) 11805 FROM 11806 employee, employee_department 11807 WHERE 11808 employee_department.employee_id = employee.employeeid 11809 AND 11810 employee_department.department_id = d.departmentid 11811 ) AS department_employees 11812 FROM 11813 store s 11814 LEFT JOIN employee manager ON s.manager = manager.employeeid 11815 LEFT JOIN department d ON d.store_id = s.storeid 11816 LEFT JOIN employee department_head ON department_head.employeeid = d.department_head 11817 LEFT JOIN employee_department ed ON ed.department_id = d.departmentid 11818 LEFT JOIN employee e ON e.employeeid = ed.employee_id 11819 LEFT JOIN sale sa ON sa.employee_id = e.employeeid 11820 LEFT JOIN customer c ON c.customerid = sa.customer_id 11821 LEFT JOIN album al ON al.albumid = sa.album_id 11822 LEFT JOIN artist ar ON ar.artistid = al.artist_id", 11823 array( 11824 'rootTag' => 'music_company', 11825 'rowTag' => 'store', 11826 'idColumn' => 'storeid', 11827 'mapper' => 'strtoupper', 11828 'attributes' => array( 11829 'storeid' 11830 ), 11831 'elements' => array( 11832 'store_sales', 11833 'store_employees', 11834 'manager' => array( 11835 'idColumn' => 'manager_employeeid', 11836 'attributes' => array( 11837 'manager_employeeid' 11838 ), 11839 'elements' => array( 11840 'manager_employeename' 11841 ) 11842 ), 11843 'address' => array( 11844 'elements' => array( 11845 'country', 11846 'state' => '#Helper::getStatePostalCode()', 11847 'city', 11848 'street', 11849 'phone' 11850 ) 11851 ), 11852 'department' => array( 11853 'idColumn' => 'departmentid', 11854 'mapper' => 'Mappers::departmentMapper', 11855 'attributes' => array( 11856 'departmentid' 11857 ), 11858 'elements' => array( 11859 'department_sales', 11860 'department_employees', 11861 'departmentname', 11862 'department_head' => array( 11863 'idColumn' => 'department_head_employeeid', 11864 'attributes' => array( 11865 'department_head_employeeid' 11866 ), 11867 'elements' => array( 11868 'department_head_employeename' 11869 ) 11870 ), 11871 'employees' => array( 11872 'rootTag' => 'employees', 11873 'rowTag' => 'employee', 11874 'idColumn' => 'employeeid', 11875 'mapper' => array('Mappers', 'employeeMapper'), 11876 'attributes' => array( 11877 'employeeid' 11878 ), 11879 'elements' => array( 11880 'employeename', 11881 'sales' => array( 11882 'rootTag' => 'sales', 11883 'rowTag' => 'sale', 11884 'idColumn' => 'saleid', 11885 'mapper' => array($myMappers, 'saleMapper'), 11886 'attributes' => array( 11887 'saleid' 11888 ), 11889 'elements' => array( 11890 'timestamp', 11891 'customer' => array( 11892 'idColumn' => 'customerid', 11893 'mapper' => false, 11894 'attributes' => array( 11895 'customerid' 11896 ), 11897 'elements' => array( 11898 'first_name', 11899 'last_name', 11900 'email' 11901 ) 11902 ), 11903 'album' => array( 11904 'idColumn' => 'albumid', 11905 'mapper' => 'XML_Query2XML_ISO9075Mapper::map', 11906 'attributes' => array( 11907 'albumid' 11908 ), 11909 'elements' => array( 11910 'title', 11911 'published_year', 11912 'comment' => '?#Helper::summarizeComment(12)', 11913 'artist' => array( 11914 'idColumn' => 'artistid', 11915 'mapper' => 'mapArtist', 11916 'attributes' => array( 11917 'artistid' 11918 ), 11919 'elements' => array( 11920 'name', 11921 'birth_year', 11922 'birth_place', 11923 'genre' 11924 ) 11925 ) 11926 ) // album elements 11927 ) //album array 11928 ) //sales elements 11929 ) //sales array 11930 ) //employees elements 11931 ) //employees array 11932 ) //department elements 11933 ) // department array 11934 ) //root elements 11935 ) //root 11936); //getXML method call 11937 11938$root = $dom->firstChild; 11939$root->setAttribute('date_generated', date("Y-m-d\TH:i:s", 1124801570)); 11940 11941header('Content-Type: application/xml'); 11942 11943$dom->formatOutput = true; 11944print $dom->saveXML(); 11945 11946require_once 'File.php'; 11947$fp = new File(); 11948$fp->write('case08.profile', $query2xml->getProfile(), FILE_MODE_WRITE); 11949 11950 11951/**Static class that provides validation and parsing methods for 11952* generating XML. 11953* 11954* It is static so that we can easyly call its methods from inside 11955* Query2XML using eval'd code. 11956*/ 11957class Helper 11958{ 11959 /**Associative array of US postal state codes*/ 11960 public static $statePostalCodes = array( 11961 'ALABAMA' => 'AL', 'ALASKA' => 'AK', 'AMERICAN SAMOA' => 'AS', 'ARIZONA' => 'AZ', 'ARKANSAS' => 'AR', 'CALIFORNIA' => 'CA', 11962 'COLORADO' => 'CO', 'CONNECTICUT' => 'CT', 'DELAWARE' => 'DE', 'DISTRICT OF COLUMBIA' => 'DC', 'FEDERATED STATES OF MICRONESIA' => 'FM', 11963 'FLORIDA' => 'FL', 'GEORGIA' => 'GA', 'GUAM' => 'GU', 'HAWAII' => 'HI', 'IDAHO' => 'ID', 'ILLINOIS' => 'IL', 'INDIANA' => 'IN', 11964 'IOWA' => 'IA', 'KANSAS' => 'KS', 'KENTUCKY' => 'KY', 'LOUISIANA' => 'LA', 'MAINE' => 'ME', 'MARSHALL ISLANDS' => 'MH', 'MARYLAND' => 'MD', 11965 'MASSACHUSETTS' => 'MA', 'MICHIGAN' => 'MI', 'MINNESOTA' => 'MN', 'MISSISSIPPI' => 'MS', 'MISSOURI' => 'MO', 'MONTANA' => 'MT', 11966 'NEBRASKA' => 'NE', 'NEVADA' => 'NV', 'NEW HAMPSHIRE' => 'NH', 'NEW JERSEY' => 'NJ', 'NEW JESEY' => 'NJ', 'NEW MEXICO' => 'NM', 'NEW YORK' => 'NY', 11967 'NORTH CAROLINA' => 'NC', 'NORTH DAKOTA' => 'ND', 'NORTHERN MARIANA ISLANDS' => 'MP', 'OHIO' => 'OH', 'OKLAHOMA' => 'OK', 'OREGON' => 'OR', 11968 'PALAU' => 'PW', 'PENNSYLVANIA' => 'PA', 'PUERTO RICO' => 'PR', 'RHODE ISLAND' => 'RI', 'SOUTH CAROLINA' => 'SC', 'SOUTH DAKOTA' => 'SD', 11969 'TENNESSEE' => 'TN', 'TEXAS' => 'TX', 'UTAH' => 'UT', 'VERMONT' => 'VT', 'VIRGIN ISLANDS' => 'VI', 'VIRGINIA' => 'VA', 'WASHINGTON' => 'WA', 11970 'WEST VIRGINIA' => 'WV', 'WISCONSIN' => 'WI', 'WYOMING' => 'WY' 11971 ); 11972 11973 /**Translates a US state name into its two-letter postal code. 11974 * If the translation fails, $state is returned unchanged 11975 * @param $record The record 11976 */ 11977 public static function getStatePostalCode($record) 11978 { 11979 $state = $record["state"]; 11980 $s = str_replace(" ", " ", trim(strtoupper($state))); 11981 if (isset(self::$statePostalCodes[$s])) { 11982 return self::$statePostalCodes[$s]; 11983 } else { 11984 return $state; 11985 } 11986 } 11987 11988 function summarize($str, $limit=50, $appendString=' ...') 11989 { 11990 if (strlen($str) > $limit) { 11991 $str = substr($str, 0, $limit - strlen($appendString)) . $appendString; 11992 } 11993 return $str; 11994 } 11995 11996 function summarizeComment($record, $limit) 11997 { 11998 return self::summarize($record["comment"], $limit); 11999 } 12000} 12001?> 12002 ]]> 12003 </programlisting> 12004 The lines 36-38 do the debugging, line 40 and 222-224 the profiling. This will create 12005 case08.log and case08.profile. 12006 </para> 12007 </refsect3> 12008 <refsect3 id="{@id case08_log}"> 12009 <title>case08.log</title> 12010 <para> 12011 The format of a debug log file is documented at {@tutorial XML_Query2XML.pkg#debugging}. 12012 Our debug log contains just a single query: 12013 <![CDATA[ 12014Apr 20 13:33:47 XML_Query2XML [info] QUERY: SELECT 12015 s.*, 12016 manager.employeeid AS manager_employeeid, 12017 manager.employeename AS manager_employeename, 12018 d.*, 12019 department_head.employeeid AS department_head_employeeid, 12020 department_head.employeename AS department_head_employeename, 12021 e.*, 12022 sa.*, 12023 c.*, 12024 al.*, 12025 ar.*, 12026 (SELECT COUNT(*) FROM sale WHERE sale.store_id = s.storeid) AS store_sales, 12027 (SELECT 12028 COUNT(*) 12029 FROM 12030 sale, employee, employee_department 12031 WHERE 12032 sale.employee_id = employee.employeeid 12033 AND 12034 employee_department.employee_id = employee.employeeid 12035 AND 12036 employee_department.department_id = d.departmentid 12037 ) AS department_sales, 12038 (SELECT 12039 COUNT(*) 12040 FROM 12041 employee, employee_department, department 12042 WHERE 12043 employee_department.employee_id = employee.employeeid 12044 AND 12045 employee_department.department_id = department.departmentid 12046 AND 12047 department.store_id = s.storeid 12048 ) AS store_employees, 12049 (SELECT 12050 COUNT(*) 12051 FROM 12052 employee, employee_department 12053 WHERE 12054 employee_department.employee_id = employee.employeeid 12055 AND 12056 employee_department.department_id = d.departmentid 12057 ) AS department_employees 12058 FROM 12059 store s 12060 LEFT JOIN employee manager ON s.manager = manager.employeeid 12061 LEFT JOIN department d ON d.store_id = s.storeid 12062 LEFT JOIN employee department_head ON department_head.employeeid = d.department_head 12063 LEFT JOIN employee_department ed ON ed.department_id = d.departmentid 12064 LEFT JOIN employee e ON e.employeeid = ed.employee_id 12065 LEFT JOIN sale sa ON sa.employee_id = e.employeeid 12066 LEFT JOIN customer c ON c.customerid = sa.customer_id 12067 LEFT JOIN album al ON al.albumid = sa.album_id 12068 LEFT JOIN artist ar ON ar.artistid = al.artist_id 12069Apr 20 13:33:47 XML_Query2XML [info] DONE 12070 ]]> 12071 </para> 12072 </refsect3> 12073 <refsect3 id="{@id case08_profile}"> 12074 <title>case08.profile</title> 12075 <para> 12076 The format of the output is documented under 12077 {@tutorial XML_Query2XML.pkg#profiling.getprofile}: 12078 <![CDATA[ 12079FROM_DB FROM_CACHE CACHED AVG_DURATION DURATION_SUM SQL 120801 0 false 0.0101430416 0.0101430416 SELECT 12081 s.*, 12082 manager.employeeid AS manager_employeeid, 12083 manager.employeename AS manager_employeename, 12084 d.*, 12085 department_head.employeeid AS department_head_employeeid, 12086 department_head.employeename AS department_head_employeename, 12087 e.*, 12088 sa.*, 12089 c.*, 12090 al.*, 12091 ar.*, 12092 (SELECT COUNT(*) FROM sale WHERE sale.store_id = s.storeid) AS store_sales, 12093 (SELECT 12094 COUNT(*) 12095 FROM 12096 sale, employee, employee_department 12097 WHERE 12098 sale.employee_id = employee.employeeid 12099 AND 12100 employee_department.employee_id = employee.employeeid 12101 AND 12102 employee_department.department_id = d.departmentid 12103 ) AS department_sales, 12104 (SELECT 12105 COUNT(*) 12106 FROM 12107 employee, employee_department, department 12108 WHERE 12109 employee_department.employee_id = employee.employeeid 12110 AND 12111 employee_department.department_id = department.departmentid 12112 AND 12113 department.store_id = s.storeid 12114 ) AS store_employees, 12115 (SELECT 12116 COUNT(*) 12117 FROM 12118 employee, employee_department 12119 WHERE 12120 employee_department.employee_id = employee.employeeid 12121 AND 12122 employee_department.department_id = d.departmentid 12123 ) AS department_employees 12124 FROM 12125 store s 12126 LEFT JOIN employee manager ON s.manager = manager.employeeid 12127 LEFT JOIN department d ON d.store_id = s.storeid 12128 LEFT JOIN employee department_head ON department_head.employeeid = d.department_head 12129 LEFT JOIN employee_department ed ON ed.department_id = d.departmentid 12130 LEFT JOIN employee e ON e.employeeid = ed.employee_id 12131 LEFT JOIN sale sa ON sa.employee_id = e.employeeid 12132 LEFT JOIN customer c ON c.customerid = sa.customer_id 12133 LEFT JOIN album al ON al.albumid = sa.album_id 12134 LEFT JOIN artist ar ON ar.artistid = al.artist_id 12135 12136TOTAL_DURATION: 0.36341714859009 12137DB_DURATION: 0.20340394973755 12138 ]]> 12139 </para> 12140 </refsect3> 12141 </refsect2> 12142 <refsect2 id="{@id finalnotes}"> 12143 <title>Final Notes on the case studies</title> 12144 <para> 12145 The files of all cases are installed in $PHP_PEAR_DOC_DIR/XML_Query2XML/cases/. 12146 </para> 12147 </refsect2> 12148 </refsect1> 12149 12150 <refsect1 id="{@id sqlddl}"> 12151 <title>SQL DDL used in all examples</title> 12152 <para> 12153 In all the examples a MySQL database created with the SQL DDL shown below is used. 12154 You might also want to have a look at the 12155 {@link http://query2xml.sourceforge.net/docs/Query2XML_Tests.jpg ER diagram}. 12156 Note: the unit tests by default use the SQLite 2 database found at 12157 $PHP_PEAR_TEST_DIR/XML_Query2XML/tests/Query2XML_Tests. If you want to run 12158 all unit tests you have to install 12159 the MySQL DDL $PHP_PEAR_TEST_DIR/XML_Query2XML/tests/Query2XML_Tests.sql 12160 or the PostgreSQL DDL $PHP_PEAR_TEST_DIR/XML_Query2XML/tests/Query2XML_Tests.psql. 12161 </para> 12162 <para> 12163 Please we use a certain naming convention for primary and foreign keys: 12164 <itemizedlist> 12165 <listitem> 12166 For primary keys we use "<table_name>id". 12167 </listitem> 12168 <listitem> 12169 For foreign keys we use "<foreign_table_name>_id". 12170 </listitem> 12171 </itemizedlist> 12172 <![CDATA[ 12173CREATE DATABASE Query2XML_Tests; 12174USE Query2XML_Tests; 12175 12176 12177CREATE TABLE artist ( 12178 artistid INT NOT NULL AUTO_INCREMENT, 12179 name VARCHAR(255), 12180 birth_year Int, 12181 birth_place VARCHAR(255), 12182 genre VARCHAR(255), 12183 UNIQUE (artistid), 12184 PRIMARY KEY (artistid) 12185); 12186 12187CREATE TABLE customer ( 12188 customerid INT NOT NULL AUTO_INCREMENT, 12189 first_name VARCHAR(255), 12190 last_name VARCHAR(255), 12191 email VARCHAR(255), 12192 UNIQUE (customerid), 12193 PRIMARY KEY (customerid) 12194); 12195 12196CREATE TABLE album ( 12197 albumid INT NOT NULL AUTO_INCREMENT, 12198 artist_id INT NOT NULL, 12199 title VARCHAR(255), 12200 published_year Int, 12201 comment VARCHAR(255), 12202 UNIQUE (albumid), 12203 PRIMARY KEY (albumid), 12204 FOREIGN KEY (artist_id) REFERENCES artist (artistid) 12205); 12206 12207CREATE TABLE employee ( 12208 employeeid INT NOT NULL AUTO_INCREMENT, 12209 employeename VARCHAR(255), 12210 UNIQUE (employeeid), 12211 PRIMARY KEY (employeeid) 12212); 12213 12214CREATE TABLE store ( 12215 storeid INT NOT NULL AUTO_INCREMENT, 12216 manager INT NOT NULL, 12217 country VARCHAR(255), 12218 state VARCHAR(255), 12219 city VARCHAR(255), 12220 street VARCHAR(255), 12221 phone VARCHAR(255), 12222 building_xmldata TEXT, 12223 UNIQUE (storeid), 12224 PRIMARY KEY (storeid), 12225 FOREIGN KEY (manager) REFERENCES employee (employeeid) 12226); 12227 12228CREATE TABLE department ( 12229 departmentid INT NOT NULL AUTO_INCREMENT, 12230 store_id INT NOT NULL, 12231 department_head INT NOT NULL, 12232 departmentname VARCHAR(255), 12233 UNIQUE (departmentid), 12234 PRIMARY KEY (departmentid), 12235 FOREIGN KEY (department_head) REFERENCES employee (employeeid), 12236 FOREIGN KEY (store_id) REFERENCES store (storeid) 12237); 12238 12239CREATE TABLE employee_department ( 12240 employee_id INT NOT NULL, 12241 department_id INT NOT NULL, 12242 PRIMARY KEY (employee_id,department_id), 12243 FOREIGN KEY (employee_id) REFERENCES employee (employeeid), 12244 FOREIGN KEY (department_id) REFERENCES department (departmentid) 12245); 12246 12247CREATE TABLE sale ( 12248 saleid INT NOT NULL AUTO_INCREMENT, 12249 album_id INT NOT NULL, 12250 customer_id INT NOT NULL, 12251 employee_id INT NOT NULL, 12252 store_id INT NOT NULL, 12253 timestamp Timestamp(14), 12254 UNIQUE (saleid), 12255 PRIMARY KEY (saleid), 12256 FOREIGN KEY (employee_id) REFERENCES employee (employeeid), 12257 FOREIGN KEY (album_id) REFERENCES album (albumid), 12258 FOREIGN KEY (customer_id) REFERENCES customer (customerid), 12259 FOREIGN KEY (store_id) REFERENCES store (storeid) 12260); 12261 12262 12263 12264INSERT INTO artist (artistid, name, birth_year, birth_place, genre) VALUES(1, 'Curtis Mayfield', 1920, 'Chicago', 'Soul'); 12265INSERT INTO artist (artistid, name, birth_year, birth_place, genre) VALUES(2, 'Isaac Hayes', 1942, 'Tennessee', 'Soul'); 12266INSERT INTO artist (artistid, name, birth_year, birth_place, genre) VALUES(3, 'Ray Charles', 1930, 'Mississippi', 'Country and Soul'); 12267 12268INSERT INTO album (albumid, artist_id, title, published_year, comment) VALUES(1, 1, 'New World Order', 1990, 'the best ever!'); 12269INSERT INTO album (albumid, artist_id, title, published_year, comment) VALUES(2, 1, 'Curtis', 1970, 'that man\'s got somthin\' to say'); 12270INSERT INTO album (albumid, artist_id, title, published_year, comment) VALUES(3, 2, 'Shaft', 1972, 'he\'s the man'); 12271 12272INSERT INTO customer (customerid, first_name, last_name, email) VALUES(1, 'Jane', 'Doe', 'jane.doe@example.com'); 12273INSERT INTO customer (customerid, first_name, last_name, email) VALUES(2, 'John', 'Doe', 'john.doe@example.com'); 12274INSERT INTO customer (customerid, first_name, last_name, email) VALUES(3, 'Susan', 'Green', 'susan.green@example.com'); 12275INSERT INTO customer (customerid, first_name, last_name, email) VALUES(4, 'Victoria', 'Alt', 'victory.alt@example.com'); 12276INSERT INTO customer (customerid, first_name, last_name, email) VALUES(5, 'Will', 'Rippy', 'will.wippy@example.com'); 12277INSERT INTO customer (customerid, first_name, last_name, email) VALUES(6, 'Tim', 'Raw', 'tim.raw@example.com'); 12278INSERT INTO customer (customerid, first_name, last_name, email) VALUES(7, 'Nick', 'Fallow', 'nick.fallow@example.com'); 12279INSERT INTO customer (customerid, first_name, last_name, email) VALUES(8, 'Ed', 'Burton', 'ed.burton@example.com'); 12280INSERT INTO customer (customerid, first_name, last_name, email) VALUES(9, 'Jack', 'Woo', 'jack.woo@example.com'); 12281INSERT INTO customer (customerid, first_name, last_name, email) VALUES(10, 'Maria', 'Gonzales', 'maria.gonzales@example.com'); 12282 12283INSERT INTO employee (employeeid, employeename) VALUES(1, 'Michael Jones'); 12284INSERT INTO employee (employeeid, employeename) VALUES(2, 'Susi Weintraub'); 12285INSERT INTO employee (employeeid, employeename) VALUES(3, 'Steve Hack'); 12286INSERT INTO employee (employeeid, employeename) VALUES(4, 'Joan Kerr'); 12287INSERT INTO employee (employeeid, employeename) VALUES(5, 'Marcus Roth'); 12288INSERT INTO employee (employeeid, employeename) VALUES(6, 'Jack Mack'); 12289INSERT INTO employee (employeeid, employeename) VALUES(7, 'Rita Doktor'); 12290INSERT INTO employee (employeeid, employeename) VALUES(8, 'David Til'); 12291INSERT INTO employee (employeeid, employeename) VALUES(9, 'Pia Eist'); 12292INSERT INTO employee (employeeid, employeename) VALUES(10, 'Hanna Poll'); 12293INSERT INTO employee (employeeid, employeename) VALUES(11, 'Jim Wells'); 12294INSERT INTO employee (employeeid, employeename) VALUES(12, 'Sandra Wilson'); 12295 12296INSERT INTO store (storeid, manager, country, state, city, street, phone, building_xmldata) VALUES(1, 1, 'US', 'New York', 'New York', 'Broadway & 72nd Str', '123 456 7890', '<building><floors>4</floors><elevators>2</elevators><square_meters>3200</square_meters></building>'); 12297INSERT INTO store (storeid, manager, country, state, city, street, phone, building_xmldata) VALUES(2, 2, 'US', 'New York', 'Larchmont', 'Palmer Ave 71', '456 7890', '<building><floors>2</floors><elevators>1</elevators><square_meters>400</square_meters></building>'); 12298 12299INSERT INTO department (departmentid, store_id, department_head, departmentname) VALUES(1, 1, 1, 'Sales'); 12300INSERT INTO department (departmentid, store_id, department_head, departmentname) VALUES(2, 1, 4, 'Marketing'); 12301INSERT INTO department (departmentid, store_id, department_head, departmentname) VALUES(3, 2, 7, 'Sales'); 12302INSERT INTO department (departmentid, store_id, department_head, departmentname) VALUES(4, 2, 10, 'Marketing'); 12303 12304INSERT INTO employee_department (employee_id, department_id) VALUES(1, 1); 12305INSERT INTO employee_department (employee_id, department_id) VALUES(2, 1); 12306INSERT INTO employee_department (employee_id, department_id) VALUES(3, 1); 12307INSERT INTO employee_department (employee_id, department_id) VALUES(4, 2); 12308INSERT INTO employee_department (employee_id, department_id) VALUES(5, 2); 12309INSERT INTO employee_department (employee_id, department_id) VALUES(6, 2); 12310INSERT INTO employee_department (employee_id, department_id) VALUES(7, 3); 12311INSERT INTO employee_department (employee_id, department_id) VALUES(8, 3); 12312INSERT INTO employee_department (employee_id, department_id) VALUES(9, 3); 12313INSERT INTO employee_department (employee_id, department_id) VALUES(10, 4); 12314INSERT INTO employee_department (employee_id, department_id) VALUES(11, 4); 12315INSERT INTO employee_department (employee_id, department_id) VALUES(12, 4); 12316 12317INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (1, 1, 1, 1, 1, '2005-05-25 16:32:00'); 12318INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (2, 2, 1, 7, 2, '2005-06-05 12:56:00'); 12319INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (3, 3, 1, 2, 1, '2005-07-10 11:03:00'); 12320INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (4, 4, 1, 8, 2, '2005-07-10 10:03:00'); 12321INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (5, 5, 1, 3, 1, '2005-07-10 13:03:00'); 12322INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (6, 6, 1, 9, 2, '2005-07-10 14:03:00'); 12323INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (7, 7, 1, 1, 1, '2005-07-10 15:03:00'); 12324INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (8, 8, 1, 7, 2, '2005-07-10 16:03:00'); 12325INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (9, 9, 1, 2, 1, '2005-07-10 18:03:00'); 12326INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (10, 10, 1, 8, 2, '2005-07-10 19:03:00'); 12327INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (11, 1, 2, 7, 2, '2005-05-25 16:23:00'); 12328INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (12, 3, 2, 8, 2, '2005-07-10 11:56:00'); 12329INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (13, 5, 2, 9, 2, '2005-07-10 13:12:00'); 12330INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (14, 7, 2, 7, 2, '2005-07-10 15:09:00'); 12331INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (15, 9, 2, 8, 2, '2005-07-10 18:49:00'); 12332INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (16, 2, 3, 1, 1, '2005-06-05 12:56:12'); 12333INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (17, 4, 3, 2, 1, '2005-07-10 10:03:32'); 12334INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (18, 6, 3, 3, 1, '2005-07-10 14:03:52'); 12335INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (19, 8, 3, 1, 1, '2005-07-10 16:03:01'); 12336INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (20, 10, 3, 2, 1, '2005-07-10 19:03:50'); 12337 ]]> 12338 </para> 12339 </refsect1> 12340 12341 <refsect1 id="{@id copyright_notice}"> 12342 <title>Copyright Notice</title> 12343 <para> 12344 Copyright: Lukas Feiler 2006 12345 </para> 12346 <para> 12347 <emphasis>LICENSE:</emphasis>This source file is subject to version 2.1 of the LGPL that is bundled with this package in the file LICENSE. 12348 </para> 12349 </refsect1> 12350</refentry>