In this section, we discuss the optional packages that are available with PEAR, how to find out about them, how to find new ones, and how to install the ones you need. However, our focus is the optional HTML Integrated Template (IT) package , which is used to separate HTML presentation from PHP code. We show you how to use templates in an application, present working examples, and detail the key functions from the package and its extended child ITX templates. A longer template case study is presented in Chapter 16. After our discussion of the IT package, we list the other packages that are available, and point to where selected packages are used in other chapters of this book. 7.3.1 Installing, Upgrading, and Understanding PackagesThis section describes how to install and upgrade PEAR packages, and how to find out more information about them. For Unix platforms, including Mac OS X, the instructions are valid for PHP 4.3.0 or later versions. For Microsoft Windows, PEAR installation and upgrade is available after PHP 4.3.2. 7.3.1.1 Finding out about packagesAt the time of writing, the PEAR documentation available at http://pear.php.net/manual/en/ was incomplete. However, you'll find some useful information there, particularly about the core components and a handful of the popular optional packages. To go beyond the limited documentation, the first step is to access the package browser at http://pear.php.net/packages.php or search for a known package directly at http://pear.php.net/package-search.php. You can also access the same information using the PEAR installer, as described later in this section. This process provides you with concise information that sums up a package, and often a link to the project's homepage that may contain more details and code examples. To begin to use a package, the best approach is to install it, review the source code of the package, and read any relevant postings to the php.pear.general newsgroup which is accessible at http://news.php.net/. To review the source code, you can follow two approaches: first, visit the source at http://cvs.php.net/cvs.php/pear/; or, second, view it on your system after the install process using the approach described later in this section. This process can sometimes be laborious but it's worth remembering that many of these packages are new, emerging, and supported to different degrees by their development teams. Reading source code is definitely worthwhile. 7.3.1.2 Using the PEAR installerYou need an Internet connection to complete this section. As we discussed previously, a list of the packages installed with your PHP installation can be obtained at a shell prompt in a Unix system (when you're logged in as root) using: % pear list On a Microsoft Windows platform, you do this in a command window with: C:\> pear.bat list In the remainder of this section, we only list the Unix commands and show a Unix shell prompt. To use the command in Microsoft Windows, replace pear with pear.bat. To find out about one of the packages installed on your system, you can ask for details or visit the package browser at http://pear.php.net/packages.php. For example, to find out more about the HTML_Template_IT package type: % pear info HTML_Template_IT In response, you'll get a page of information that describes the package, its current release, licensing requirements, and its release state. The release state is one of Stable, Beta, Alpha, or Devel(opment). The majority of packages are Stable, with the remaining majority in Beta testing. Use non-stable packages with caution. If your computer has an Internet connection, you can check whether your packages can be upgraded to later releases by typing: % pear list-upgrades This requests information from the pear.php.net server. In response, the pear installer will report information such as: Available Upgrades (stable): ============================ +-------------+---------+--------+ | Package | Version | Size | | Archive_Tar | 1.0 | 12.4kB | | Mail | 1.0.2 | 12.1kB | | Net_SMTP | 1.1.2 | 5.2kB | | PEAR | 1.0.1 | 75kB | | XML_Parser | 1.0.1 | 4.9kB | You can obtain the same information by browsing the packages at the PEAR web site http://pear.php.net/. All information is for stable releases of packages, which can be safely installed and used. You can choose to upgrade a specific package or upgrade all out-of-date packages. For example, to upgrade only the PEAR base class: % pear upgrade PEAR The PEAR installer retrieves the package and responds with: downloading PEAR-1.0.1.tar ... ...done: 395,776 bytes upgrade ok: PEAR 1.0.1 Sometimes, the upgrade process can fail but helpful information is provided as to why. For example, an upgrade of the Net_SMTP package often fails for PHP 4.3.2: % pear upgrade Net_SMTP downloading Net_SMTP-1.1.2.tar ... ...done: 29,184 bytes requires package `Auth_SASL' Net_SMTP: dependencies failed In order to proceed, the Auth_SASL package is needed first. This can be achieved with: % pear install Auth_SASL You can then try again to install the Net_SMTP package using pear upgrade. As discussed in the previous section, viewing source code is an excellent method to understand how a package works. After you've installed a package, you'll find it below the directory /usr/local/lib/php/ on most Unix systems, in /usr/local/php/lib/php under Mac OS X, or in C:\Program Files\EasyPHP1-7\php\pear\pear on a Microsoft Windows system if you've followed our PHP installation instructions in Appendix A to Appendix C. The majority of the core packages are in the directory itself, while the optional packages are stored in subdirectories that are named according to the package category. For example, the IT templates that are discussed in the next section are found in the HTML subdirectory. Other popular package categories are listed later in Section 7.3.3. The PEAR installer isn't always reliable or well-configured on every system. If you have problems installing or upgrading packages, you can download packages from the PEAR web site and put them in the PEAR directories manually. This is also a useful trick for getting PEAR packages working without root or administrator user access: download the packages you need, uncompress them, put them in a directory that you've created, and then include the package file in your PHP script. 7.3.2 Using HTML TemplatesSeparating code from HTML can be difficult in PHP. As we discussed in Chapter 1 and have shown so far in this book, one of the best features of PHP is that scripts can be embedded anywhere in HTML documents. However, this can lead to maintenance problems: if you want to redesign the presentation of the web site, you may need to rewrite code or, at the very least, understand how PHP and HTML are interleaved in the application. This also makes it difficult to maintain code when it is interleaved with presentational components. A good solution for medium- to large-scale web database applications is to use templates to separate markup and code. In this section, we illustrate how PEAR Integrated Templates (IT) can be used in PHP applications through simple examples, and also show you an example with Extended Integrated Templates (ITX). There are many other good templating environments, including a few others in PEAR itself. Outside of PEAR, the Smarty PHP template engine is popular and flexible, and available from http://www.phpinsider.com/php/code/Smarty. To use the IT and ITX packages, you need to install the package HTML_Template_IT . To do this, you can follow the general instructions in the previous section or the detailed instructions for Linux, Mac OS X, and Microsoft Windows platforms in Appendices A to C. 7.3.2.1 Working with blocks and placeholdersIn our first example, we show you how to develop the basic components needed in most templated PHP code: an HTML template and its accompanying PHP script. Our aim in this example is to display a list of customers in an HTML table environment. The customer data is stored in a customer table in MySQL that was created with the following statement: CREATE TABLE customer ( cust_id int(5) NOT NULL, surname varchar(50), firstname varchar(50), initial char(1), title_id int(3), address varchar(50), city varchar(50), state varchar(20), zipcode varchar(10), country_id int(4), phone varchar(15), birth_date char(10), PRIMARY KEY (cust_id) ) type=MyISAM; Example 7-2 is the template that acts as a placeholder to show selected customer information. In our example, the template is saved in the file example.7-2.tpl and stored in a templates directory below our main htdocs directory that contains the PHP scripts. Perhaps the most surprising thing about a template is that it is usually well-formed HTML 4.01. Indeed, when it's viewed in a Mozilla browser as shown in Figure 7-1, it has the features of a customer listing but with only one row and with placeholders in curly braces shown instead of the customer details. We always use uppercase characters for our placeholders so that they stand out in the template and in the code, but this isn't required.
The other difference between a typical HTML page and a template is that the template contains comments that include the tags BEGIN and END. These comment tags are in pairs that have matching labels that define a block . In this example, there's one block that has the label CUSTOMER. Blocks represent units of information that are optional or can repeat. In Example 7-2, the CUSTOMER block surrounds a prototype data row that is output once for each customer in the database. In our PHP script, we control the presentation by assigning each row of data from the database to the block, and then parsing and outputting the completed row as HTML to the browser. Example 7-2. A template for displaying customer details<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html401/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <title>Customer Details</title> <body> <table> <tr><th>Name<th>Address<th>City<th>State<th>Zipcode <!-- BEGIN CUSTOMER --> <tr><td>{FIRSTNAME} {SURNAME}<td>{ADDRESS} <td>{CITY}<td>{STATE}<td>{ZIPCODE} <!-- END CUSTOMER --> </table> </body> </html> The template, as viewed in a Mozilla browser, is shown in Figure 7-1 (to view it, we renamed the .tpl file with an .html extension). Figure 7-1. The customer template from Example 7-2 viewed in a Mozilla browser.The script that populates the template with the customer data is shown in Example 7-3. To access the DBMS, no new functionality is included: the script uses the query process explained in Chapter 6 to connect, query, and extract results. What is new is the use of templates: the script itself doesn't output data using print but instead assigns data to elements of the customer template. Example 7-3. A PHP script that populates the customer template in Example 7-2<?php require_once "HTML/Template/IT.php"; include "db.inc"; // Connect to the MySQL server if (!($connection = @ mysql_connect($hostname, $username, $password))) die("Cannot connect"); if (!(mysql_select_db($databaseName, $connection))) showerror( ); // Run the query on the connection if (!($result = @ mysql_query ("SELECT * FROM customer LIMIT 50", $connection))) showerror( ); // Create a new template, and specify that the template files are // in the subdirectory "templates" $template = new HTML_Template_IT("./templates"); // Load the customer template file $template->loadTemplatefile("example.7-2.tpl", true, true); while ($row = mysql_fetch_array($result)) { // Work with the customer block $template->setCurrentBlock("CUSTOMER"); // Assign the row data to the template placeholders $template->setVariable("FIRSTNAME", $row["firstname"]); $template->setVariable("SURNAME", $row["surname"]); $template->setVariable("ADDRESS", $row["address"]); $template->setVariable("CITY", $row["city"]); $template->setVariable("STATE", $row["state"]); $template->setVariable("ZIPCODE", $row["zipcode"]); // Parse the current block $template->parseCurrentBlock( ); } // Output the web page $template->show( ); ?> The code fragment: require_once "HTML/Template/IT.php"; loads PEAR's Integrated Template class into the script. After this, we create a new IT template $template, and specify that the templates we're using are found in the templates subdirectory: $template = new HTML_Template_IT("./templates"); (The period and forward slash in ./templates means the templates directory is a subdirectory of the directory that contains the PHP script.) After that, we load in our template file from Example 7-2: $template->loadTemplatefile("example.7-2.tpl", true, true); The two additional parameters are discussed later in this section. Having set up our template, we can now use it with the data from our query. This is a three-step process:
This process is repeated each time we want to output a block (which, in this case, is a row of customer data). By default, if you don't use a block, it's assumed you don't want to output it and it isn't included in the HTML output. In our script, the repeating three-step process is encapsulated in the following code fragment: // Work with the customer block $template->setCurrentBlock("CUSTOMER"); // Assign the row data to the template placeholders $template->setVariable("FIRSTNAME", $row["firstname"]); $template->setVariable("SURNAME", $row["surname"]); $template->setVariable("ADDRESS", $row["address"]); $template->setVariable("CITY", $row["city"]); $template->setVariable("STATE", $row["state"]); $template->setVariable("ZIPCODE", $row["zipcode"]); // Parse the current block $template->parseCurrentBlock( ); The parameter to HTML_Template_IT::setCurrentBlock( ) is the name of the block you want to work with in the template file. The parameters to HTML_Template_IT::setVariable( ) are a placeholder name within the block, and the data to assign to the placeholder. The method HTML_Template_IT::parseCurrentBlock( ) processes the currently selected block. The script repeats the three-step process until there's no more data to process from the query. After that, the entire web page is output by the statement: $template->show( ); The result of running the script and using the template is shown in Figure 7-2. Figure 7-2. The output of running Example 7-3 shown in a Mozilla web browser7.3.2.2 Nested blocksExample 7-4 shows a more complex template example. This template is designed to display information about wine regions and, for each region, a list of the wineries that are situated there. We use two database tables in our example, winery and region. These are created with the following statements: CREATE TABLE winery ( winery_id int(4) NOT NULL, winery_name varchar(100) NOT NULL, region_id int(4) NOT NULL, PRIMARY KEY (winery_id), ); CREATE TABLE region ( region_id int(4) NOT NULL, region_name varchar(100) NOT NULL, PRIMARY KEY (region_id), ); There's a one-to-many relationship between the tables: each winery row has a region_id attribute that stores the identifier of a row in the region table. The blocks in the template in Example 7-4 are nested. The REGION block spans most of the HTML listing and contains within it a WINERY block. The nesting matches the table relationship: there can be many region blocks, and many winery blocks within each region. Example 7-4. A template with nested blocks for showing regions and wineries<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html401/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <title>Regions and Wineries</title> <body> <ul> <!-- BEGIN REGION --> <li>Region: {REGIONNAME} <ul> <!-- BEGIN WINERY --> <li>{WINERYNAME}. <!-- END WINERY --> </ul> <!-- END REGION --> </ul> </body> </html> Sample output from Example 7-4 is shown in Figure 7-3. Figure 7-3. The output of running Example 7-4 shown in a Mozilla web browserExample 7-5 shows the PHP script that works with the template. The logic of the script flows similarly to the template. The three-step process of selecting a block, assigning data to placeholders, and parsing is repeated for each region in the database. Nested inside that looping process, the same three steps occur for the wineries within each region. One simple rule needs to be followed when a nested template is used: the innermost block must be parsed first, followed by the second innermost block, and so on until the outermost block has been parsed. In our example, this means that each repeating WINERY block must be parsed before the REGION block it belongs in. After all blocks that need to be populated have been parsed, the web page is output using HTML_Template_IT::show( ). Example 7-5. The PHP script that works with the template in Example 7-4<?php require_once "HTML/Template/IT.php"; require "db.inc"; if (!($connection = @ mysql_connect($hostname, $username, $password))) die("Cannot connect"); if (!(mysql_select_db($databaseName, $connection))) showerror( ); if (!($regionresult = @ mysql_query ("SELECT * FROM region LIMIT 10", $connection))) showerror( ); $template = new HTML_Template_IT("./templates"); $template->loadTemplatefile("example.7-4.tpl", true, true); while ($regionrow = mysql_fetch_array($regionresult)) { $template->setCurrentBlock("REGION"); $template->setVariable("REGIONNAME", $regionrow["region_name"]); if (!($wineryresult = @ mysql_query ("SELECT * FROM winery WHERE region_id = {$regionrow["region_id"]}", $connection))) showerror( ); while ($wineryrow = mysql_fetch_array($wineryresult)) { $template->setCurrentBlock("WINERY"); $template->setVariable("WINERYNAME", $wineryrow["winery_name"]); $template->parseCurrentBlock( ); } $template->setCurrentBlock("REGION"); $template->parseCurrentBlock( ); } $template->show( ); ?>
7.3.2.3 Preserving and removing blocksIn our previous example, we used the following fragment to load a template file: $template->loadTemplatefile("example.7-4.tpl", true, true); The second and third parameters specify sensible default behavior for working with unused placeholders and blocks. The second parameter specifies that if a block isn't used in a template, it shouldn't be output. This is useful if you have an optional block that that's sometimes used, as we discuss in the next section. The third parameter behaves similarly for placeholders: when set to true, placeholders that haven't had data assigned to them are removed during parsing. If you have chosen to remove empty blocks but need to preserve a block at runtime, this is possible using the HTML_Template_IT::touchBlock( ) method. Touching means a block is marked as needing to be output, even if nothing has been assigned to its placeholders. For example, to preserve a DETAILS block you can use: $template->touchBlock("DETAILS"); This is a useful feature in two situations: first, when you want to output a block but don't want to assign data to its placeholders; or, second, if a block has no placeholders and you want it to be shown. We show you an example in the next section. 7.3.2.4 More on nesting and optional blocksBlocks aren't always nested: if data isn't related, it shouldn't be nested. For example, if we wanted to output information about the ten most popular wineries and the ten best customers, we might use the following template: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html401/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <title>Details</title> <body> <h1>Our best customers</h1> <!-- BEGIN CUSTOMER --> Name: {FIRSTNAME SURNAME} <!-- END CUSTOMER --> <h1>Our most popular wineries</h1> <!-- BEGIN WINERY --> Name: {WINERYNAME} <!-- END WINERY --> </body> </html> In this structure, we can choose to repeat the CUSTOMER block zero or more times, and to independently repeat the WINERY block zero or more times. There is no relationship between the two blocks, and it doesn't matter whether you work with the customers or wineries first. Unrelated, unnested blocks can be assigned and parsed in any order. However, regardless of how you process and assign the data, all CUSTOMER blocks will always appear before all WINERY blocks because that's how the template is structured. In the previous example, if you don't assign any data to the CUSTOMER block, the heading Our best customers will still be output because it isn't part of a block that you can control in your code. Moving the heading inside the CUSTOMER block doesn't solve the problem because the heading would then be repeated for each customer. One solution is to add another unnested block to the template so that the heading is optional: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html401/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <title>Details</title> <body> <!-- BEGIN CUSTOMERHEADING --> <h1>Our best customers</h1> <!-- END CUSTOMERHEADING --> <!-- BEGIN CUSTOMER --> Name: {FIRSTNAME SURNAME} <!-- END CUSTOMER --> <h1>Our most popular wineries</h1> <!-- BEGIN WINERY --> Name: {WINERYNAME} <!-- END WINERY --> </body> </html> You can then use program logic to choose whether to output a CUSTOMERHEADING or not, depending on whether there are any CUSTOMER blocks being used. Note, however, that the CUSTOMERHEADING block doesn't contain any placeholders, and so with the default behavior you'll need to call $template->touchBlock("CUSTOMERHEADING") so that it's displayed in the output. Our previous example can be improved. To avoid having to use program logic and the HTML_Template_IT::touchBlock( ) method, you can restructure the template so that it's nested: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html401/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <title>Details</title> <body> <!-- BEGIN CUSTOMERHEADING --> <h1>Our best customers</h1> <!-- BEGIN CUSTOMER --> Name: {FIRSTNAME SURNAME} <!-- END CUSTOMER --> <!-- END CUSTOMERHEADING --> <h1>Our most popular wineries</h1> <!-- BEGIN WINERY --> Name: {WINERYNAME} <!-- END WINERY --> </body> </html> This works better because the CUSTOMERHEADING block contains the CUSTOMER block. With nesting, if the CUSTOMER block is used, there's no need to use HTML_Template_IT::touchBlock( ) on CUSTOMERHEADING. Similarly, if nothing is assigned to a CUSTOMER block, CUSTOMERHEADING hasn't been touched and won't be output. This is another example of our basic relationship rule: if data is related, use nesting; if it isn't, don't. So far, we've dealt with related and unrelated blocks that appear in a fixed order. Sometimes, however, you may want to display data in an arbitrary order that you want to be flexible at runtime. To do this, you can create a block that contains several nested blocks at the same level. For example, if we wanted to output several red, green, or blue messages in any order on a page, we could use the following template: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html401/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <title>Color lines</title> <body> <!-- BEGIN COLORLINES --> <!-- BEGIN RED --> <font color="red">{MESSAGE}</font> <!-- END RED --> <!-- BEGIN GREEN --> <font color="green">{MESSAGE}</font> <!-- END GREEN --> <!-- BEGIN BLUE --> <font color="blue">{MESSAGE}</font> <!-- END BLUE --> <!-- END COLORLINES --> </body> </html> So, to output a blue line, you select the BLUE block, assign the data to MESSAGE placeholder, parse the BLUE block, and then select and parse the COLORLINES block. To output another color, you repeat the same process for that different colored block. Using this technique, the COLORLINES block repeats, but with each repeat you can choose a different inner block. In Chapter 10 and Chapter 16, we explain a template for displaying form widgets that uses this technique. 7.3.2.5 Extended Integrated Templates (ITX)Optional blocks allow most of the flexibility you'll need to develop template applications. However, sometimes you may need to dynamically create a template at runtime. Usually, this is done by using a main template file, and then adding template fragments in the PHP script. A popular use of this technique is to store a standard header and footer for an application in a main template file, and then to dynamically add the page body at runtime. This is what we do in our sample application in Chapter 16 through Chapter 20. The Extended Integrated Template (ITX) class extends IT templates, adding the functionality to dynamically construct templates at runtime. The following is an example main template file stored in the file about_today.tpl: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html401/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <title>About Today</title> </head> <body> {MESSAGE} </body> </html> The placeholder MESSAGE is the position at which our choice of template fragment is inserted. In this example, our script will insert a different template depending on the day of the week. If today is a weekday, the following template fragment stored in the file weekday.tpl is inserted: Oh no. It's {DAY}, which is a weekday. If today is on a weekend, this fragment stored in the file weekend.tpl is inserted: Good news. It's {DAY}, which is on the weekend. All of the template files are stored in the same directory. The script that works with the templates is as follows: <?php require_once "HTML/Template/ITX.php"; $template = new HTML_Template_ITX('.'); $template->loadTemplatefile("about_today.tpl", true, true); $daynumber = date("w"); // Is it a weekday? if ($daynumber != 0 && $daynumber != 6) // Include the weekday template fragment $template->addBlockFile("MESSAGE", "NEWMESSAGE", "weekday.tpl"); else // Include the weekend template fragment $template->addBlockFile("MESSAGE", "NEWMESSAGE", "weekend.tpl"); $template->setCurrentBlock("NEWMESSAGE"); $template->setVariable("DAY", date("l")); $template->parseCurrentBlock( ); $template->show( ); ?> Rather than work with IT.php library, this script uses the ITX.php file. The ITX templates provide several new methods, the most useful of which is HTML_Template_ITX::addBlockFile( ). This method takes three parameters: the placeholder to replace, the name of the block to replace it with, and the template fragment file that forms the body of the block. In our example, depending on the day of week determined with the date( ) function, a choice is made as to whether to replace the MESSAGE placeholder with the fragment weekday.tpl or the fragment weekend.tpl; the date( ) function is discussed in Chapter 3. After the replacement, the script proceeds in the same way as previous examples by selecting our new block, assigning values to placeholders, parsing, and outputting the results. The template fragments do not include the name of the new block, this is supplied as the second parameter to HTML_Template_ITX::addBlockFile( ). As we've shown you, the HTML_Template_ITX::addBlockFile( ) inserts a file into a template at a location defined by a placeholder and then redefines the replaced section as a block. Blocks can also be replaced at runtime by other blocks using the HTML_Template_ITX::replaceBlockFile( ) method that's explained in the next section. 7.3.2.6 Essential IT and ITX functions
7.3.3 Optional PackagesOf the 154 PEAR packages at the time of writing, this section lists the 40 most popular as determined by download frequency at http://pear.php.net/package-stats.php. As discussed at the beginning of this section, the complete list is available by requesting the package installer to provide the information or by browsing packages in the repository. Selected packages are used in other sections of the book, and we've noted this next to those packages. 7.3.3.1 AuthenticationThere are six packages available, and two are popular.
7.3.3.2 BenchmarkingThere's only one package, and it's in the top 40.
7.3.3.3 CachingTwo packages are available, and one is popular.
7.3.3.4 ConsoleFive packages are available. The popular one is the core component Console_Getopt for retrieving command-line arguments from non-web scripts. 7.3.3.5 DatabaseThere are fourteen packages, of which four are popular.
7.3.3.6 DateThere is one package that's popular.
7.3.3.7 FilesystemThere are six packages, of which two are popular.
7.3.3.8 HTMLThere are fifteen packages, including five popular ones.
7.3.3.9 HTTPThere are six packages and three are popular.
7.3.3.10 InternationalizationThere's one popular package for internationalization.
7.3.3.11 LoggingThere is one package, and it's popular.
7.3.3.12 MailThere are six packages, and two are popular.
7.3.3.13 NetworkingThere are twenty-nine packages, of which four are popular: we use the Net_DNS package (which isn't discussed here) in Chapter 9.
7.3.3.14 PEARThere are three packages and two are popular.
7.3.3.15 PHPThere are nine packages and three are popular.
7.3.3.16 XMLThere are eleven packages, of which three are popular.
7.3.3.17 Web servicesThere are three packages, of which one is popular.
|