This copy is registered to: Rodney Burruss
[email protected]
TM
CONTENTS
Features
Columns
8
4
EDITORIAL
6
php|news
An Introduction to Drupal
A modular framework for content management by TITUS BARIK
16 PHP Web Services: An Introduction to SOAP
Implementing web services with the NuSOAP Library by ALESSANDRO SFONDRINI
23 Logging and Monitoring with log4php A simple way to implement logging in your applications by KNUT URDALEN
50 TEST PATTERN Stocking Fillers Year-end book reviews by MARKUS BAKER
55 SECURITY CORNER Context by CHRIS SHIFLETT
32 Advanced SQLite Development A database solution to simplify your code and help you create high-performance applications by PETER LAVIN
60 PRODUCT REVIEW Zend Studio 5.0 by PETER MacINTYRE
SPECIAL REPORT 44 Where on the Web is PHP? PHP Version Tracker Report by DAMIEN SEGUY
68 exit(0); Ode to My Keyboard by MARCO TABINI
Download this month’s code at: http://www.phparch.com/code/
WRITE FOR US!
If you want to bring a php-related topic to the attention of the professional php community, whether it is personal research, company software, or anything else, why not write an article for php|architect? If you would like to contribute, contact us and one of our editors will be happy to help you hone your idea and turn it into a beautiful article for our magazine. Visit www.phparch.com/writeforus.php or contact our editorial team at
[email protected] and get started!
EDITORIAL
I
THE PHP EMPIRE
f you follow PHP releases, closely, you’ll have noticed that PHP 5.1.0 was released on Thursday November 24, 2005. This release was billed as paramount in PHP’s history, touted by many core developers as “the first solid PHP 5 release.” This supposed banner release was quickly overshadowed on the developers’ mailing list as problematic: a change that was made late in the release candidacy cycle (a new internal Date class) will break hundreds, if not thousands (or perhaps hundreds of thousands?) of deployed PHP applications. It was generally accepted knowledge that new functionality can’t break existing code, but this “knowledge” proved simply incorrect. In this case, PEAR has an existing (and well-deployed) Date class of its own. As you know, PHP doesn’t currently have namespacing, so you obviously cannot have a Date class in PHP and then declare a second one for PEAR. Much discussion ensued, including a strong push towards an accepted namespace standard for PHP (perhaps we’ll see it in the 5.x series, and all signs point to namespaces in PHP 6 at the latest). The really interesting discussion for me, though was something along the lines of “why should core care about PEAR?” Yet another political discussion. It seems that the technical problems are (relatively) easy to solve, and the real problem is on the social side. So, should PHP core care about PEAR? Tough call. On one hand core should be given first-rights to any sort of class, function, variable or constant name. The other side (again) is social: when most users download and install a package from PEAR, they do so from pear.php.net. As far as those users are concerned, they’re downloading both PHP and PEAR from php.net, so they should work together. Unfortunately, there’s no easy and permanent solution to this problem. However, Ilia, the 5.1 release manager—and past author for php|architect—wisely reacted quickly, and on November 28, 2005, PHP 5.1.1 was released, temporarily solving this problem, by removing the new internal Date class. One growing pain averted, if only temporarily. PHP’s integration and many offerings from php.net gives it somewhat of an “empire” status. This metaphor is further extended when see how PHP has conquered much of the web in its short lifetime. Tips & Tricks takes a break this month to make way for a special feature called “Where in the World is PHP?” In this piece, Damien Seguy shares his company’s findings on how PHP has infiltrated nearly every market, territory, and size of web application. This boom is no accident. As you saw last month, PHP is making great strides in the enterprise, and is shedding its “quick & dirty” reputation. Long live the dynamically typed variable!
Volume 4 - Issue 12 Publisher Marco Tabini
Editor-in-Chief Sean Coates
Editorial Team Arbi Arzoumani Peter MacIntyre Eddie Peloke
Graphics & Layout Aleksandar Ilievski
Managing Editor Emanuela Corso
News Editor Leslie Hill
[email protected] Authors Marcus Baker, Titus Barik, Peter Lavin, Peter B. MacIntyre, Robert Mark, Damien Seguy, Alessandro Sfondrini, Chris Shiflett, Knut Urdalen php|architect (ISSN 1709-7169) is published twelve times a year by Marco Tabini & Associates, Inc., P.O. Box 54526, 1771 Avenue Road, Toronto, ON M5M 4N5, Canada. Although all possible care has been placed in assuring the accuracy of the contents of this magazine, including all associated source code, listings and figures, the publisher assumes no responsibilities with regards of use of the information contained herein or in all associated material. php|architect, php|a, the php|architect logo, Marco Tabini & Associates, Inc. and the Mta Logo are trademarks of Marco Tabini & Associates, Inc.
Contact Information: General mailbox:
[email protected] Editorial:
[email protected] Subscriptions:
[email protected] Sales & advertising:
[email protected] Technical support:
[email protected] Printed in Canada Copyright © 2003-2005 Marco Tabini & Associates, Inc. All Rights Reserved
4 • php|architect • Volume 4 Issue 12
news PHPBase Alpha PHP 5.5.1 “The PHP Development Team would like to announce the immediate release of PHP 5.1.1. This is a regression correction release aimed at addressing several issues introduced by PHP 5.1.0, the core changes as follows: • Native date class is withdrawn to prevent namespace conflict with PEAR’s date package. • Fixed fatal parse error when the last line of the script is a PHP comment. • eval() hangs when the code being evaluated ends with a comment. • Usage of \{$var} in PHP 5.1.0 resulted in the output of {$var} instead of the $var variable’s value enclosed in {}. • Fixed inconsistency in the format of PHP_AUTH_DIGEST between Apache 1 and 2 sapis.\ • Improved safe_mode/open_basedir checks inside the cURL extension. Get your hands on the latest release at php.net!
Phpbase.org announces their alpha release. What is it? According to phpbase.org, “PhpBase is a set of Open Source PHP classes and functions aimed to help developers submitting their data to Google Base. The main purpose for a tool like this is the need to keep data submissions accurate and avoid common errors that might occur when submitting to standard schemes recommended by Google. The code still in development, and will be added to a subversion repository once we get the project approved at berlios.de.” Get all the latest info from phpbase.org.
PHP applications across the enterprise. Zend Studio 5, the most widely used Integrated Development Environment for PHP, has been enhanced with new features to assist in the development, debugging, testing, and deployment of PHP applications, including easy integration with other enterprise applications. Available in three editions; Standard, Professional, and Enterprise, Zend Studio 5 includes support for PHP 5 as well as an easy switching mechanism allowing users to move between PHP 4 and PHP 5 for full application development, and also seamlessly integrates with Zend Platform 2 to provide a complete development and deployment solution for business-critical, PHP-based applications.” Visit Zend.com to grab the latest release and check out all the latest features.
OBM-Open Business Management 1.0.2 ZEND Studio 5 Zend.com announces their latest release of the Zend Studio, version 5. “The product is a full Life Cycle development solution that allows developers to easily and more effectively create, test, and deploy their
OBM is proud to announce the latest release of their Business Management application version 1.0.2. Need a free management application for your business? Check out OBM. According to OBM’s site: “BM is an Intranet application which goal is to help manage a company or an organization.
php|architect Releases New Design Patterns Book We’re proud to announce the release of php|architect’s Guide to PHP Design Patterns, the latest release in our Nanobook series. You have probably heard a lot about Design Patterns —a technique that helps you design rock-solid solutions to practical problems that programmers everywhere encounter in their day-to-day work. Even though there has been a lot of buzz, however, no-one has yet come up with a comprehensive resource on design patterns for PHP developers—until today. Author Jason E. Sweat’s book php|architect’s Guide to PHP Design Patterns is the first, comprehensive guide to design patterns designed specifically for the PHP developer. This book includes coverage of 16 design patterns with a specific eye to their applications in PHP when building complex web applications, both in PHP 4 and PHP 5 (where appropriate, sample code for both versions of the language is provided). For more information, http://www.phparch.com/shop_product.php?itemid=96.
6 • php|architect • Volume 4 Issue 12
As OBM improve, it is approaching what are called CRM (with sales force , help desk, time tracking sections) but can be used simply as a contact database or as a shared calendar. OBM represents a framework above which many modules are written.” Interested? Get more info from http://obm.aliacom.fr/.
PHPDocumentor 1.3.0 RC4 Phpdoc.org announces the latest release of phpdocumentor 1.3.0 RC4. What’s new? • Full PHP 5 support, phpDocumentor both runs in and parses Zend Engine 2 language constructs. Note that you must be running phpDocumentor in PHP 5 in order to parse PHP 5 code • XML:DocBook/peardoc2: default converter now beautifies the source using PEAR’s XML_Beautifier if available • inline {@example} tag - this works just like {@source} except that it displays the contents of another file. • customizable README/ INSTALL/CHANGELOG files • phpDocumentor tries to run .ini files out of the current directory first, to allow you to put them anywhere you want • multi-national characters are now allowed in package/ subpackage names • images in tutorials • un-modified output • html/xml source highlighting This release also contains many bug fixes. Get the latest info and start documenting at phpdoc.org.
Volume 4 Issue 12 • php|architect •7
Check out the hottest new releases from PEAR.
PHP_Compat 1.5.0RC1
PHP_Compat provides missing functionality for older versions of PHP. Note: PHP_Compat can be used without installing PEAR. 1) Download the package by clicking the “Download” link. 2) Find the file you need. Each function is in its own file, e.g. array_walk_ recursive.php. 3) Place this file somewhere in your include_path. 4) Include it, e.g. The function is now ready to be used.
HTML_QuickForm_ advmultiselect 1.1.0
The HTML_QuickForm_advmultiselect package adds an element to the HTML_ QuickForm package that is two select boxes next to each other emulating a multi-select.
Net_IPv4 1.3.0
Class used for calculating IPv4 (AF_INET family) address information such as network as network address, broadcast address, and IP address validity.
HTML_AJAX 0.3.1
Provides PHP and JavaScript libraries for performing AJAX (Communication from JavaScript to your browser without reloading the page)
Log 1.9.3
The Log framework provides an abstracted logging system. It supports logging to console, file, syslog, SQL, Sqlite, mail, and mcal targets. It also provides a subject - observer mechanism.
File_SearchReplace 1.1.0
Provides various functions to perform search/ replace on files. Preg/Ereg regex supported along with faster but more basic str_replace routine.
Cache_Lite 1.6.0
This package is a little cache system optimized for file containers. It is fast and safe (because it uses file locking and/or anti-corruption tests).
Net_DNS 1.0.0rc3
A resolver library used to communicate with a name server to perform DNS queries, zone transfers, dynamic DNS updates, etc. Creates an object hierarchy from a DNS server response, which allows you to view all of the information given by the DNS server. It bypasses the system resolver library and communicates directly with the server.
Looking for a new PHP Extension? Check out the latest from PECL.
hash 1.0
Native implementations of common message digest algorithms using a generic factory method.
ps 1.3.3
ps is an extension similar to the pdf extension but for creating PostScript files. Its api is modelled after the pdf extension.
Fileinfo 1.0.2
This extension allows retrieval of information regarding vast majority of files. This information may include dimensions, quality, length etc. Additionally, it can also be used to retrieve the mime type for a particular file and, for text files, the proper language encoding.
sdo 0.7.0
Service Data Objects (SDOs) enable PHP applications to work with data from different sources (like a database query, an XML file, or a spreadsheet) using a single interface.
FEATURE
an introduction Organizations these days are demanding content management applications, from company homepages to large, community websites. Let Drupal help you build these sites quickly and efficiently by building on a common, modular framework.
by TITUS BA RIK
I
n their every day development, most web consulting companies encounter many of the same components when designing pages for nonprofit organizations and activist groups. These requirements include forums, a donation system, membership management, newsletters, and articles, just to name a few. Many of these organizations also have small to mid-size e-commerce needs, to effectively sell and distribute promotional materials, or to handle signups for paid events. Developers often tackle this task by rolling their own systems in an attempt to make their sites as customizable as possible, while still providing core components to build on for each individual client. In theory, it sounds like a good idea. In practice, the limitations of their underlying frameworks ultimately result in nothing more than a mash of hacks that only loosely integrate the unique elements required for their clients. In the worst case, web development shops end up coding the entire thing from scratch, each and every
8 • php|architect • Volume 4 Issue 12
PHP: 4.4 O/S: Debian OTHER SOFTWARE: Drupal 4.6.3 LINKS: Drupal Handbook:
http://drupal.org/handbook
Drupal Modules:
http://drupal.org/project/Modules
PHP Template:
http://drupal.org/project/phptemplate
Democratica Theme, by Chris Messina, part of the CivicSpace distribution of Drupal: http://drupal.org/handbook
TO DISCUSS THIS ARTICLE VISIT: http://forum.phparch.com/272
An Introduction to Drupal
time. Somehow, rolling your own system never quite works out the way it needs to. Even if you have two well-written components, it is often the case that it’s not the components themselves that are the problem, but the integration points that add burdensome complexity. Integrating disjoint components, say Forum A with Shopping Cart B, has until now, been a difficult problem to solve. This article introduces Drupal, a modular, open source content management platform that attempts to address these difficult challenges. Drupal provides a flexible, extensible framework—an alternative to roll-your-own content management solutions. It can be refit for a variety of different content systems, including community portal sites, personal weblogs, and resource directories, simply by adding and removing sophisticated modules. Drupal offers you a working, tested framework for building components, and handles the insignificant but tedious details of module management, user management, and integration, so you can focus on developing the core of your project. More importantly, Drupal has a very large user base, and modules to handle most common functionality are already available. When these modules don’t exist, the hooks system allows the developer to create custom modules to interact with the Drupal core, while leveraging the existing Drupal building blocks. It’s not at all surprising, then, that the focus of this article is not on complex development or PHP code, at least not directly. In fact, it’s quite the opposite. In this article, we’ll develop a mock political organization’s web site, Democratica, without writing a single line of code. Along the way, I’ll demostrate the various 9 • php|architect • Volume 4 Issue 12
built-in and contributed modules that allow you to get a site up running quickly and effortlessly.
Installation This section discusses the installation and configuration details of Drupal, including the pre-requisite software requirements. Our test installation consists of Drupal 4.6.3, PHP 4, and Apache 2, running on Linux with a MySQL 4.1 database server. Though Drupal supports both MySQL and PostgrseSQL, be advised that most available third-party modules are coded specifically for MySQL. This effectively forces you to use the former, regardless of FIGURE 1
The first screen you’ll encounter after installing Drupal.
what the Drupal core modules support. Drupal allows you to host multiple sites on a single Drupal instance. These sites can be found under the sites folder. For simplicity, I’ll only utilitize a single site setup in this article. After creating and assigning the appropriate permissions to your database, load the tables: mysql -u nobody -p drupal < database/database.mysql
Next, enter the sites/default folder and modify settings.php; namely, modify the db_url, and base_url. Drupal works best on dedicated hosting systems. Since Drupal
An Introduction to Drupal memory requirements increase proportionally with the number of active modules, the default PHP memory limit of 8 MB provided by most shared hosting providers is typically not enough. To be safe, set this value to 32M or 64M in php.ini. If the memory requirements are not successfully met, Drupal will behave strangely, displaying broken administrative menus and erratic page rendering. Next, add the following to your crontab: 0
*
*
*
*
wget -O - -q http://democratica/cron.php
This allows for scheduled events to trigger properly within Drupal. Such events include search indexing, mass mailing, and scheduled publishing. That’s all there is to the installation. Navigate to your Drupal site using your web browser, and you’ll be presented with a base page, as shown in Figure 1.
Finding Your Way Around It’s time to create your first account. User #1 is provided with full administrative access. All subsequent users are given authorized user access. Additional groups can be generated though the Drupal system as well, providing fine granularity amongst different group types. In Democratica, for example, we may want special access for employees, and different access priveleges for members and guests, After logging in, select the administer menu. You’ll be presented with Drupal’s recent system events, a web-based logging system for monitoring the activity on your web site. A typical administration page is shown in Figure 2. For now, enter the settings menu, and change the
default name of the site to Democratica. You should also set your time zone. Feel free to modify any other settings, as well. Perhaps the two most useful menus under administer are blocks and modules. The modules menu allows you to extend the core functionality of Drupal. Within this menu, you’ll activate and install module components to tailor the functionality that your site requires. For example, you could enable search, forums, events, and so forth. The blocks menu enables and disables boxes that can be positioned on the left and right side bars of your web site. The available blocks vary, depending on which modules are currently active. Under modules, notice that the page and story modules are checked by default. Now, jump up to the create content menu. You’ll notice that there are two available content types: page and
FIGURE 2
Administration section of Drupal FIGURE 4
FIGURE 3
The Drupal donation block. 10 • php|architect • Volume 4 Issue 12
Administration section of Drupal
An Introduction to Drupal story, the same as the modules we previously witnessed. Enabling content modules will add additional content types to this menu. After initial setup, most interaction with the Drupal site will occur through this menu. Finally, let’s examine the content menu, again under administer. This menu allows us to configure the properties of content nodes, which at first, can be a bit perplexing. Unlike a dedicated content system such as WordPress for blogging, or osCommerce for e-commerce, Drupal aims to be flexible, to suit any type of web platform. In order to provide such flexiblility, the fundamental type of almost all content is that of a simple node. Custom behavior is then implemented by simulating the base node type, and subsequently adding or removing properties to that node type. The content menu allows us to set additional properties, modify nodes, and add custom nodes to facilitate our development process.
designers, for example, can independently develop multiple, pluggable layouts, while your logic developers can write custom modules and other core functionality.
Modules The remainder of this article will focus on modules. The process of installing some modules is more involved than others, though none of the modules are exceptionally difficult to integrate. The typical installation procedure for a module is as follows: First, load the database schema. This is usually one or more iterations of the following command: mysql -u user -p database < schema.sql
Next, copy the module to the modules directory. Then activate the module in the administer modules menu.
Unlike a dedicated content system such as WordPress for blogging, or osCommerce for e-commerce, Drupal aims to be flexible, to suit any type of web platform. Democratica Theme In Drupal, themes are rendered through the use of template engines. Unfortunately, Drupal by default only provides xtemplate, and the Democratica theme that we’re interested in requires PHPTemplate. Luckily, this takes very little effort. Simply download the PHPTemplate engine from the Drupal web site, and extract it to the themes/engines folder. Next, download and extract the Democratica theme, and place it in your themes folder. Login to your web site as administrator and access the themes menu. The Democratica theme is now automatically available. Set it to default, and save your configuration. Your new theme is now applied. Theming is a powerful component of the Drupal framework that will greatly streamline development. Namely, it enables you to parallelize your backend business logic and your presentation code. Your graphic 11 • php|architect • Volume 4 Issue 12
Then, set configuration parameters for the module under the administer settings menu, and configure access control permissions on the module through the access control menu. Finally, if the module has a presentation layer, enable the block in the block menu, specifying its location and ordering. Some modules have dependencies on other modules. Many modules, for example, depend on Flexinode, a module that allows non-programmers to create new node types from within the content types menu, without having to custom load database node schemas for each new node type. Before continuing, install and activate the Flexinode module. A content type menu will appear under content, and a flexinode menu item will appear under settings.
An Introduction to Drupal
Donations Our first module will be a simple one. Most political organizations have a need for donations, and our site is no different. The Donations module allows us to
through the simplenews module. Install it now. Like the donation module, it will appear as though nothing has changed. Click the new newsletters menu item under administration; this will create a default Drupal
Drupal provides an excellent, out of the box, event handling system. accomplish just this goal, with seamless integration to the Donorge.org donation service. Under settings, modify the text as desired. Since this isn’t a live site, the donation ID doesn’t actually matter. Nothing appears to have actually happened, but that’s because we haven’t told Drupal to place the content block on the web site. Let’s do that now. Click on the blocks menu, enable Donations, and place it on the right sidebar. A third column on the Democratica theme will now be enabled, with your new donation module, as shown in Figure 3. Without having to modify a single line of code, we’ve managed to add donation functionality to our Democratica site. Reusable components such as the donations module are what make developing under the Drupal framework so efficient.
FIGURE 5
Election survey.
FIGURE 6
Survey
Newsletter After interviewing several non-profit organizations, the most demanded feature next to donations is that of newsletters. Newsletters, when used effectively, are a vital method of directly addressing your members, notifying them of important news and issues. Our political organization, in particular, wants to let our subscribers know about the latest campaign news. Drupal provides such functionality 12 • php|architect • Volume 4 Issue 12
newsletters. Under the newsletters tab, click edit type for Drupal, and change the name to “Democratica Newsletter.” The Newsletters interface allows you to create multiple newsletters, track the progress of newsletters, and track subscribers, as shown in Figure 4. Under blocks, activate the newly created newsletter, placing it on the right sidebar. Next, create a newsletter from the create content menu. Creating a newsletter item is simple enough that it doesn’t require any further explanation here. Assuming your PHP configuration and mail server are setup correctly, all subscribers will receive an e-mailed copy of the newsletters you’ve just posted.
Election survey, administrator perspective
Our political organization wants to collect data that may help in predicting the outcome of the next election. To do this, they would like to create a survey, asking various questions on the web site. At first glance, such a task seems like a great deal of work. We’d have to design a backend database to store a variety of survey questions,
An Introduction to Drupal implement forms on the front-end, and provide facilities to allow the organization to collect and download the data in a user-friendly format. In addition, we would have to provide some sort of dynamic form creation system to allow users to create their own surveys. Unsurprisingly, Drupal makes this task simple and reusable through the survey module. The survey module depends on the forms module, which provides an API for adding usercustomizable form elements within modules. First, install the form module and activate it. Then, install and activate the survey module. Under the create content menu, the survey item will appear, in addition to the standard story and page items. Let’s now create a survey called “Election Survey.” After entering the general information, you’ll find yourself in a survey menu, from where you’ll go to the form tab. Add a radio field. We’d like to know which political party you belong to. Under selection options, enter “Republican; Democrat; Libertarian”. The semi-colon is used to delimit the multiple options for the given field type. Add any additional desired fields. A non-administrator user will be presented with the survey, as shown in Figure 5. As an administrator, your window will appear more like that in Figure 6. When a user completes a survey, his or her results can be found under the responses tab.
FIGURE 8
Content type administration for an Event flexinode
FIGURE 9
modules. In addition, activate the cod module and the tangible module, for tangible products. Under create content, a product item will now appear. Here, you can set the product title, description, price, and the type of product. For Democratica, the fulfillment house handles all inventory, and we thus disable inventory management. Under the block menu, enable the Shopping Cart module. Under the create content menu, create your product. Once completed, it will appear in the top-level products menu. From here, you can add it to your cart and checkout. The e-commerce module can and probably will need to be customized through PHP to suit your particular needs. However, the provided shopping cart already provides much of the required base functionality.
Events
Democratica can’t be successful in rallying support if it can’t successfully organize events. Drupal provides an excellent, out of the box, event handling system. The event module relies on the Flexinode module, which was installed earlier in this article. A close cousin of the event module is the location module, which Content Types submenu for Content administration allows for additional location E-Commerce information. Throughout the political campaign, our organization The location module will add the final touch to wants to sell tangible items through its fulfillment house. our event system. Currently, the location module fully These items include bumper stickers, signs, buttons, and supports United States zip codes. The module provides so on. routines for conducting postal code proximity searches, The e-commerce package includes several modules. linking to Google maps, and other functions for collecting Some of the modules are located in Figure 7. locations. Follow the directions for installing this module, E-commerce preferences can set under the settings carefully. This module is unique in that it requires you to menu. Among other options, items include things like load three mysql data files: a location schema, a zipcode whether customers must create an account to order, schema, and US zip code data. Then, just as we’ve done shipping methods, and payment methods. for all other modules, activate it through the module For our purposes, activate all of the required menu. 13 • php|architect • Volume 4 Issue 12
An Introduction to Drupal After enabling both modules, access the block menu RSVP and enable List of upcoming events. An empty upcoming The Democratica organization is pleased with the events events listing will appear, depending on your sidebar system. However, only special guests are invited to the selection. Democratica Launch Party, based on, say, the number of Unlike most of our previous modules, an event is not charitable contributions, and our currents events system a node type in itself. Specifically, event properties can be has no way to track this. added to any node type. So, for example, you can create This is where the RSVP module comes in. RSVP lets your own Generic Event node type using Flexinodes, or users invite people to attend an event. It sends an you can create a Birthday node type for birthdays, and so invitation email to a list of people, and can the track on, to describe different categories of events. which users have looked at the invitation and their Let’s create a Generic Event node now. Click on content responses. Invitees can view and reply without having type in the content menu under administration, and click user accounts. the add content type tab. Call our content type name After enabling the RSVP option, a Create RSVP link “event,” and give it a short blurb in the description. will become active on all events. From here, you can You’ll now be placed in the list tab, where you can add invite selected guests to your event. An invite preview is additional fields to your custom node. Title is already shown in Figure 11. included, so let’s add a text area for the event description. After following these steps, your screen should resemble Other Modules Figure 8. Our last module is as big one. CiviCRM is a web-based, Though we’ve created a generic open source, internationalized, event node, we have yet to bind FIGURE 10 constituent relationship the event module properties to management (CRM) application, it. To do so, click on the content designed specifically to meet the menu, then the configure tab. needs of advocacy, non-profit and From here, click the content type non-governmental organizations. subtype, where you’ll find the In short, it keeps track of people. configure option for the event. CiviCRM is large enough to be Yes, it’s deeply buried under a lot a product on its own. It is also of menus, so look at Figure 9 for the most complicated of modules assistance. to install, and its installation Under configure, scroll down instructions would comprise an to “show in event calendar.” Set entire article by itself. The CiviCRM this to all views to enable event module is also a dependency for times for the generic event node the other modules which require that you’ve just created. Under contact management, and it is Democratica Launch Party event. the blocks menu, enable “List of also an optional requirement for upcoming events.” In addition, the event finder module, which FIGURE 11 select “Enable for location,” a allows the user to search for events feature contributed by the location based on event type, geographic module. Set “Allow” for street, city, location, and proximity to major state, postal, and country. metropolitan area. You’re on the final stretch. It’s Thankfully, the OpenNGO web time to actually add an event. Under site provides excellent installation create content, you’ll now find an instructions for the CiviCRM module. event item. Fill in the details for Here, I’ll omit the installation steps your event. In our example, we’re and show you the brief demo of the celebrating the Democratica Launch CiviCRM and Event Finder modules. Party, shown in Figure 10. Notice CiviCRM primarily enables also, that the event has been the Find Contacts feature, shown added to the upcoming events list in Figure 12. It also allows you in the sidebar. to manage groups, relationship Democratica Launch Party invite.
14 • php|architect • Volume 4 Issue 12
types
between
contacts,
and
An Introduction to Drupal location information. For those who don’t have in-house contact management tools, CiviCRM may be a good option. Event Finder takes the functionality of the existing events module and adds the ability to actively search for local events by category, event type, and zip code. It also enables online registration for events, where one can specify the maximum number of registrants.
Conclusion
FIGURE 12
CiviCRM contact search page.
FIGURE 13
The final result is shown in Figure 13. In the course of about an hour or two, you’ve created a dynamic site that provides a shopping cart, donation system, newsletters, event management, and surveys. You’ve also taken advantage of the underlying Drupal framework, including its user management and permissions systems. Even if you’re required to customize each and every one of these components for your specific project, The end result. Not bad at all for the small investment we’ve made. the time saved in comparison to writing a custom solution from scratch is simply tremendous. Drupal is a large and powerful framework, and you’ll no doubt run into a few obstacles here and there, along the way. When you do, the Drupal Handbook is available online for your use. The Drupal web site also provides forums to address questions and issues that aren’t TITUS BARIK is a content application developer with an interest in open source Enterprise solutions. He has deployed both open source covered by the Handbook. and proprietary content management systems successfully in corporate Sites that have successfully implemented Drupal and non-profit environments. His personal weblog is available at barik. include Spread Firefox, Kernel Trap, Linux Journal, and net, and he welcomes your comments and suggestions. The Onion. I hope that you’ll be next. 15 • php|architect • Volume 4 Issue 12
FEATURE
PHP Web Services: An Introduction to SOAP
PHP WEB SERVICES:
an Introduction to
SOAP
Web Services are widely used to build applications designed to share information or to communicate with remote programs. In this article we will take a look at their basic structures and purposes.
by ALESSANDRO SFONDRINI
W
hen we write a PHP application, it may rely on a remote program that performs certain operations what we can’t, or don’t want to do locally. For instance, we may need to fetch some up-to-date data about, the weather in Los Angeles. Since it is unlikely that our application will have access to this data through a local database, we must somehow communicate with the web site of the local meteorological station in L.A. We have a similar problem if we want to make our user perform a web search without leaving the pages of our site—our PHP application must send a query to (e.g.) the Google Application Program Interface, and retrieve the response. Perhaps you want to implement an online shop, with a payment system based on PayPal; you’d have to set up secure communications between your site and the PayPal servers. Finally, you may have a local database of user names, mails and contacts and want to make some of this information available to a certain number of authorized 16 • php|architect • Volume 4 Issue 12
PHP: 4.3.0+ Other Software: NuSOAP Library, MySQL database LINKS: www.w3.org/TR/soap www.w3.org/TR/wsdl dietrich.ganx4.com/nusoap CODE DIRECTORY: phpwebservices TO DISCUSS THIS ARTICLE VISIT: http://forum.phparch.com/273 users. All of the examples above require the use of some sort of communication with a remote application: what we are looking for are Web Services. A web service is a software system designed to support interoperable machine-to-machine interaction over a
PHP Web Services: An Introduction to SOAP network. So, when we talk about WS, we are implicitly referring to many parts: • A client application, which can be written in any language (e.g. C, Java, Python, and of course PHP) and run on any Operating System • A remote application, with which we want to communicate • A remote interface, often described by WSDL (Web Service Description Language) • A protocol, normally based on TCP/IP—the most used are HTTP and HTTPS • A document to be sent or received, which is usually encoded using particular XML subsets like SOAP (Simple Object Access Protocol) or XML-RPC (Remote Procedure Call) In Figure 1 you can see an example of an application which connects to a remote Application Program Interface to fetch data from its database: while the communication between the program and the user (at least, his browser) is encoded in HTML, the traffic between the two machines is transported via SOAP. This article is a short introduction to web services. Our purpose is to explore what we can do with them and how they work. We will have a closer look at SOAPbased web services over HTTP, as they are the most common, and are pretty easy to use. We will also take a look at a sample application which consists of a client & server system, and performs a simple operation (in our case, retrieves data from a remote MySQL database). Out sample takes advantage of NuSOAP, a predefined PHP library, designed to perform Remote Procedure Calls. Before we get into the real work, however, we will cover SOAP and WSDL basics, without getting too deep, just to make things a bit clearer. We won’t, however, see any complex practical application of Web Services. In fact, this article’s intent is introductory. If you’re looking for a little more meat, you should take a look at the upcoming php|a NanoBook, “Practical Web Services,” which I will quote several times in the following text.
Simple Object Access Protocol: an overview SOAP is an XML-based protocol, created by the World Wide Web Consortium. It was created to help server applications exchange structured and typed information in a distributed environment. A SOAP message can be sent on any Internet protocol (HTTP and HTTPS are the most common). What makes SOAP so powerful is that the application that receives the message doesn’t need to be written in 17 • php|architect • Volume 4 Issue 12
the same programming language, or even to be run on the same operating system as the one that originated the request. The platform is simply irrelevant—so you could easily make a .NET application exchange data with one written in Python. A SOAP message is normally composed of three elements: an envelope, a header and a body. When dealing with an error condition, a SOAP fault element will also be present. The envelope is the first element in a SOAP message, and it’s mandatory. It may contain namespace declarations or additional attributes, which must be namespace-qualified. It may also contain additional namespace-qualified sub-elements that must follow the body element. The header is optional and, if used, must be the first child element of the envelope. It may contain a set of namespace-qualified header entries as an immediate child element of the header element, and it may also contain general information about the message, such as language, date, etc. The body is mandatory, and must be an immediate child element of the envelope (and follow the header, if it’s present). It may contain a set of body entries, FIGURE 1
LISTING 1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
Moz-K527 W.A.Mozart, Don Giovanni
29.90
PHP Web Services: An Introduction to SOAP which must be an immediate child of the body element and each entry may be namespace-qualified. This is the most important part of the message, since it contains the request or the response, and the part that is used to perform Remote Procedure Calls (RPCs). As I mentioned, a particular type of body entry, named fault, is used to indicate error messages. Its subelements are used to report information about the error (the error code and name, what caused it, etc.). Any type of application may need to use this kind entry, so we will examine it, too (briefly). For a detailed description of the SOAP language, check the World Wide Web Consortium documentation at http://www.w3.org/TR/SOAP. We won’t examine WSDL in depth, as it is quite a complex topic, but we can’t completely overlook it because of its importance—it’s definitely worth a quick glance, if for nothing else, then to understand its main purpose.
SOAP Envelope element This element must always contain a namespace URI to define the SOAP versioning model. The only valid namespace URI is: http://schemas.xmlsoap.org/soap/envelope/. The envelope may also contain an encodingStyle attribute to indicate the serialization rules used; here are some examples of valid URIs: • http://schemas.xmlsoap.org/soap/encoding/ • http://example.com/enc/spec/ • http://www.example.com/enc/ As you can see, you can use more than one URI separated by a space (ordered from the most specific to the least specific). A blank URI means that there are no claims for the encoding style. Here is an example of a valid envelope element:
It has namespace and encoding style declarations. We will insert our other elements or sub-elements within this main tag.
SOAP Header Element The SOAP header is qualified by its local name and namespace URI. It is optional, and may contain general information about the message. The encodingStyle attribute may be used to indicate the encoding style. Two attributes are frequently used in this element: mustUnderstand and actor. The mustUnderstand attribute indicates whether the 18 • php|architect • Volume 4 Issue 12
header entry must be processed by the recipient. Its value can be 1 or 0 (if it’s absent, a default value of 0 is assumed). If it’s set to 1, the entry must be processed. The actor attribute is useful for messages that travel from the originator to their final destination by passing through one or more intermediaries (SOAP servers can have the ability to both receive and forward a SOAP message). actor specifies which of these intermediaries should receive and process the header entry (which, when processed, will be no longer forwarded). The value of actor is the URI of the recipient. There are, however, two special cases: if the attribute is absent, then the recipient automatically becomes the recipient; if the attribute is set to http://schemas.xmlsoap.org/soap/actor/next the destination is intended to be the first intermediary.
SOAP Body element The body element is used to send RPC calls or responses, and to report errors. A body entry is defined by its name, composed by a valid namespace URI and a local name. Immediate child elements of body element may be namespace-qualified. When sending the message over HTTP (as we will), the POST method should be used to both send the call and obtain the response. In PHP, we can do this using the socket functions. The content type we should specify is text/xml, but text/plain also works. The encoding depends on the application that we are dealing with. In the first part of Listing 1, there is an example of a valid RPC call, performed using SOAP. In this message, there is no header element, and that’s OK, since it is optional. Look at the body element: in sub-element GetPrice (defined by the URI Some-URI), we store the product code and name, to obtain the price. The second part of Listing 1 contains a possible response. Note that this is the bare XML response message, without HTTP headers (a few lines that the server sends to indicate whether the operation was successful and additional information like date and content-length). This is a very basic example of a SOAP RPC call and response. Real-life applications usually manage a larger amount of data, including arrays of sub-elements, which makes things more complex. I’ve intentionally kept this example simple, to make things a bit clearer.
SOAP Fault Element If, during the execution of the remote application, an error occurs, preventing the program from producing the expected output, it is necessary to inform the client of what happened. This is exactly the purpose of SOAP fault element. It is a child element of the envelope and the
PHP Web Services: An Introduction to SOAP LISTING 2 1 # Dump of “transactions” 2 3 CREATE TABLE transactions( 4 tid INT UNSIGNED NOT NULL AUTO_INCREMENT , 5 cid INT UNSIGNED NOT NULL , 6 description VARCHAR(30) NOT NULL , 7 amount FLOAT(7,2) UNSIGNED NOT NULL , 8 STATUS ENUM(‘pending’,’failed’,’OK’) 9 DEFAULT ‘pending’ NOT NULL , 10 PRIMARY KEY (tid) 11 );
LISTING 3 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
LISTING 4 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
19 • php|architect • Volume 4 Issue 12
only child element of the body element—which means that when a body carries a fault, no other information is passed. A fault mainly consists of a mandatory standard error code named Code, which identifies the problem (e. g. Sender or Receiver to identify the party responsible for the fault) and a mandatory Reason explaining what went wrong (e. g. Timeout). It may contain additional information, like Node which explains which node of the SOAP message path caused the error, and Role, which is used to describe the role of that node. Additionally, a Detail element is available to explain precisely what went wrong. We will actually use these fault sub-elements in our applications.
A quick glance to WSDL As I mentioned earlier, we will take only a quick look at WSDL, to better understand how web services work. WSDL is an XML-based document format created to describe Web Services. In particular, its aim is to provide information about the public interface of the web service. A WSDL document contains all the information needed about how to reach the web service—the methods to be used, the parameters they require and the type of output they will produce. If you want to learn more about WSDL, you can check the World Wide Web Consortium page about it at http://www.w3.org/TR/wsdl.
How to Write an Application The application that we will write consists of two parts: client.php (Listing 3) is designed to send a request to a remote application and fetch its response, while server.php (Listing 4) will receive the request, process it, and produce output to send as a response. As we have seen in the previous paragraphs, SOAP is simple enough that we could write the request “manually”, simply by concatenating strings and variables. The problem comes when we have to parse an XML document: there are several PHP functions designed for this purpose, but a parsing application is often quite complex to write, especially when receiving long and complex messages. Fortunately, there are open source libraries which can handle the tricky part for us. This will allow us to spend more time on the other parts of our application— security, for example, is the most important aspect of a Web Service! Of course, writing our own code is generally the best approach, as in doing so, we are fully aware of how it
PHP Web Services: An Introduction to SOAP works and how to optimize it. But this takes a lot of work, and may introduce some errors in our app—it’s not only a matter of laziness. So there isn’t a best way of coding—avoiding libraries or using them—as it all depends on our particular situation and time limits. In this article we will use NuSOAP, an open source PHP library, to simplify our task.
PHP NuSOAP library NuSOAP, a group of classes designed to allow developers to manage SOAP web services, is licensed under LGPL and is available for free at http://dietrich.ganx4.com/nusoap/. All we have to do to use it is to include nusoap.php in our scripts, by using require().
execute the RPC: we invoke the call() method, specifying the remote method name and the parameters to be passed (contained in $params). NuSOAP automatically fetches the result and stores it in the $result associative array. If we are working with a WSDL-based server (and consequently $wsdl is set to TRUE), once created, $client can also invoke the remote method in this way:
This can be useful to simplify our code: first we create a proxy client ($proxy), then we can invoke any remote
If, during the execution of the remote application, an error occurs, it is necessary to inform the client of what happened. Writing a client with this library is really easy. Instead of building a SOAP message and sending it to the remote API we can just pass to a class the variables we need and instruct it to send the SOAP-encoded variables over HTTP or any of the most common TCP/IP protocols. Let’s see an example:
First of all, we include NuSOAP, then we store the parameters we will use for the RPC in the associative array, $params. We create an object passing two arguments: the SOAP server address $remote (a string) and $wsdl, a boolean value which indicates if the server uses a WSDL document. As we have seen, a WSDL (Web Services Description Language) document contains information about the Web Service, its methods and properties and is often used by Web Services, especially if they reach a certain degree of complexity. Once we have created the object, we only have to 20 • php|architect • Volume 4 Issue 12
method specified in the WSDL document using the proxy, without having to use NuSOAP call() method. Now, let’s see how to configure a basic remote interface with NuSOAP:
Once we’ve included the library, we can create a server object and register a method that we want to make available. Then, we declare a function with the same name and instruct it to perform some operations and return a result, just as we would do for any PHP function. Finally, we pass to the server object all of the HTTP POST data we received, and it will automatically produce output.
Designing the Application Remember, our application will consist of two PHP files, both based on NuSOAP library: client.php and server.php. We won’t use a WSDL file, as our web service will be very basic. Let’s have a look to his structure. In client.php we will send a SOAP message to retrieve
PHP Web Services: An Introduction to SOAP information about a transaction, sending a transaction ID (tid) and a customer ID (cid) as parameters to the remote method, get_transaction(). We expect the remote application to send back a status code (“OK”, “pending”, “failed”), the amount of the transaction, and the two IDs (to verify that no errors have occurred). In addition, server.php will append the current value of date() to those variables. In server.php, we will first check that the remote method has been invoked in the right way (e.g. via POST), next that all the data required is provided. So, we try to retrieve the data required from our database (we will use MySQL), and send it back. In Listing 2, you can find the dump of the table we will use.
Writing the Client Look at Listing 3: first, we include NuSOAP and create the client object as we have done in the previous examples. Note that the $wsdl boolean parameter is absent: this means that it is assumed FALSE and no WSDL file is available for this Web Service. Next, we store the input data for the remote application in the $params associative array. Note that if this had been a “real life” application, the input data could have come from a form submitted by the user, from the $_SESSION array, or from our database. In order to simplify our code, let’s assume that, no matter where the data may have come from, it is now stored in this array. Now, the heavy lifting is done by NuSOAP: just one line of code to build a request, send it, retrieve and parse the response. Sounds fantastic, doesn’t it? Once the results are stored in $res, we check whether any fault occurred (we analyze the $fault variable created by NuSOAP), whether the IDs sent match the ones returned, and eventually print the result. This was a really basic program, just to get started with the simplest possible application of Web Services— a sort of “Hello World.” Writing the server will be a bit more difficult.
Writing the server file In Listing 4, you can find the code for the server. As the server file is not designed to be reached by a browser, but only by a remote application via POST, we first check whether the request method is POST; otherwise the program exits. We could also check the IP of the remote application or require a username & password. In this case, remember that to ensure secure transmission of data we must use HTTPS instead of HTTP. Next, we include NuSOAP, create the server object
21 • php|architect • Volume 4 Issue 12
($s) and register a method (get_transaction()). The interesting part of the code is this function. It requires two parameters—the transaction ID and the customer ID—which we will use to build our MySQL query. The first step of the function is to check that the data type of the parameters is “integer”; otherwise, it will produce an error message. This message is worth a closer look, as we don’t use die() but we produce a SOAP Fault document using NuSOAP soap_fault() method. We specify three properties: • the fault Code—which can be either the client or the server—is set to “Sender”, as the problem lies in the wrong data type of the input the client sent • the Reason, set to “Input err”, • and the Detail, which explains the problem that occurred Of course, using return to produce the SOAP Fault document we prevent get_transaction() from performing any other operation. Next, we build the MySQL query and store it in $q. We connect to MySQL and select a database, verifying that each step was successful—producing a Fault if it wasn’t. Once we have executed the query, we count the results returned. An error occurs if N < 1, because this means that the query produced no result—likely because the transaction ID & customer ID sequence wasn’t correct— but also the case N > 1, which is even less probable, and represents an error, as each transaction must be unique. So, we join these two conditions as N != 1. Finally, we retrieve an associative array with the results, append a time element, in which we record the value of time(), when the operation has been performed, and return the $resp array. All we have to do to execute the remote procedure call is pass the raw POST data as input to service(). So what our application will do, in the end, is fetch (over HTTP) information stored in the database of a remote machine, and make it available to our user. It’s something really simple, but the underlying idea is the same at the base of Amazon or PayPal web services.
A first step to Practical Web Services This article is just a first step to the practical use of web services, as it may be regarded as a general introduction to SOAP and to remote procedure calls. There are many other things to say about SOAP, about writing a client or server without using libraries, and also about using NuSOAP in a more sophisticated way. Of course, all of this would require a deeper theoretical introduction and—more importantly—many more
PHP Web Services: An Introduction to SOAP examples. It is clear that an article is not the right place to talk in an exhaustive way about a topic as elaborate as web services. Furthermore, even if we could have dealt with the topics above, we would still have been far away from what mainly interests a PHP developer: how can I use all these things in my job? What about payments with PayPal, or online shopping with eBay? If you are interested in any of these topics, or simply would like to have a deeper view of PHP & Web Services, you might like to know the topics of the upcoming NanoBook “Practical Web Services”.
Practical Web Services The book overlooks a complete theoretical lesson about SOAP: this article provided a brief one which is enough to start working. As the title underlines, the attention is focused on the practical aspects, so after a short introduction to Web Services, we dive straight into some code. In fact, the majority of the work is composed of examples of real-life applications, realized with many different programming techniques and commented almost line by line, sometimes with the help of pictures.
22 • php|architect • Volume 4 Issue 12
Conclusions Coordinating data with a remote application, or sharing local data with public or private clients is becoming more and more necessary as the Web evolves. For these reasons Web Services are gaining a central position in the world of web based application, and for a programmer is an often required skill. I hope that this article has been an useful introduction to SOAP and Web Services, even if represents just a starting point for a developer interested in writing applications using these technologies and acquiring an important skill. If you have any doubt about the content of this article, drop me a note at
[email protected] or leave a message on php|a online discussion board.
ALESSANDRO SFONDRINI is a young programmer from Como, Italy. His interests are mainly related to PHP and C, with a particular focus on Web Services. Alessandro is the author of the upcoming php|a Nanobook, “Practical Web Services.” You can contact him at
[email protected].
FEATURE
Logging and Monitoring with log4php
LOGGING & MONITORING WITH
log4php Monitoring your application involves keeping a close watch on your implementation and getting notified when an error occurs. Error logging is closely related to monitoring, and with log4php, you can write logging code once and configure the destination of different type of logging messages, without changing your code. This is a useful way of applying a logging mechanism to your application, especially when it will be deployed in a lot of different ways.
by KNUT URDALEN
I
n this article, I will demonstrate how you can apply and maintain logging and monitoring in your own web applications using log4php. The text is based on my experience with log4php in some of our latest in-house products at Telio. The first part contains a brief introduction with some sample code of core functionality; the second part covers some more advanced topics and useful examples on how to deal with errors, and how to apply context data to logging events, as well as some optimization tips. In the last part, I will also show you how to create a centralized logging server.
Introduction You never know what could happen with your code. Errors will always occur, either because of bugs in your own code, or an external interface or resource going down. You could have lost your network connection to the MySQL server, the hard disk could have become full, there can be bugs in third party code, or there may be bugs in your code that you did not catch before you released your product. 23 • php|architect • Volume 4 Issue 12
PHP: 4.2+ OTHER SOFTWARE: log4php LINKS: http://logging.apache.org/log4php/ CODE DIRECTORY: log4php TO DISCUSS THIS ARTICLE VISIT: http://forum.phparch.com/274
Logging in PHP applications is traditionally done by using your own logging function to send a message to a file or by mail, or through a small 3rd party logging class. However, writing logging code with a configurable logging system lets you write this code once and allows the system administrator to decide what kind of messages he wants to capture, and where those messages should be sent. When serving a mission critical application, you
Logging and Monitoring with log4php can’t afford to wait until users let you know when your application fails. In most companies, application monitoring is the responsibility of administrators or an operation team. It is critical for these attendants to establish procedures and strategies on how to handle application monitoring. Communicating these strategies to developers within the development team will help both teams work together on improving logging and monitoring information that, in the end, will help resolve small issues before they become bigger problems. It is not an easy task to apply logging to a large base of preexisting code. A better approach is to make a strategic decision early in the design phase to determine the manner in which you will apply error logging, monitoring and audit logging.
What is log4php? log4php is a port of the popular log4j open source logging
API for Java and has been around since early 2003. In looking at the adoption of log4php in open source projects and activity on the project’s own mailing lists, it does not seem that many PHP developers are actually using it. However, it is a robust and flexible logging system and does not have any real competitors. Earlier this year, log4php was adopted by Apache’s Logging Services Project whose intention is to provide crosslanguage logging services based on the work of log4j. That means that log4php will be backed up in the same way as log4j, log4cxx and log4net when the incubation stage is finished.
How does it work? Log4php logically consists of three main components: appenders, layouts and loggers. • An appender is responsible for appending a log message to a resource. Some available appenders are console, database, file, email, socket and daily rotating logfiles. • A layout is the view of an appender and how the log message should be formatted. Typically, these are HTML, XML or text based on a pattern string. • A logger combines a set of appenders with a unique name, which is used to reference the logger from program code. One and only one log level is attached to a logger. Additionally, there are two other terms that are very common: log level and root logger. • A log level is a defined severity for the log
24 • php|architect • Volume 4 Issue 12
LISTING 1 1 2 3 4
; log4php.properties log4php.appender.default = LoggerAppenderEcho log4php.appender.default.layout = LoggerLayoutSimple log4php.rootLogger = DEBUG, default
LISTING 2 1 2 3 4 5
LISTING 3 1 2 3 4
log4php.appender.default = LoggerAppenderFile log4php.appender.default.file = audit.log log4php.appender.default.layout = LoggerLayoutTTCC log4php.logger.audit = INFO, default
LISTING 4 1 2 5 6 7 8 9 10 11 12
LISTING 5 1 2 3 4 5 6 7 8 9
LISTING 6 1 2 3 4 5
log4php.appender.default = LoggerAppenderDailyFile log4php.appender.default.layout = LoggerLayoutTTCC log4php.appender.default.datePattern = Ymd log4php.appender.default.file = /path/to/dailylog_%s.log log4php.rootLogger = INFO, default
LISTING 7 1 2 3 4 5 6
log4php.appender.email = LoggerAppenderMail log4php.appender.email.layout = LoggerLayoutTTCC log4php.appender.email.from = root@localhost log4php.appender.email.to =
[email protected] log4php.appender.email.subject = Log4php test log4php.rootLogger = FATAL, email
LISTING 8 1 log4php.appender.default = LoggerAppenderConsole 2 log4php.appender.default.layout = LoggerLayoutTTCC 3 log4php.appender.default.layout.threadPrinting = true 4 log4php.appender.default.layout.categoryPrefixing = true 5 log4php.appender.default.layout.contextPrinting = false 6 log4php.appender.default.layout.microSecondsPrinting = false 7 log4php.appender.default.layout.dateFormat = %d-%m-%Y %H:%M:%S 8 log4php.rootLogger = INFO, default
Logging and Monitoring with log4php message. Standard log levels are debug, info, warn, error and fatal. • A root logger is the base logger that log events if a named logger does not exist, or when a logging event is caught by another logger. The root logger sits on the top of the logger hierarchy. To get started and be able try out some of the code in this article, you should grab the latest version of log4php from http://logging.apache.org. After unpacking the archive, you should add the src directory within log4php to your include_path so it is available from all scripts. I will start with an absolute minimal “Hello World!” example, to give you an idea of how the system works. The first thing you need to do is to set up a log4php configuration file (see Listing 1). Save this code to a file called log4php.properties. A minimal configuration should contain at least a logger and an appender. I’ve first defined an appender named “default” attaching the LoggerAppenderEcho class and giving it a simple layout using the LoggerLayoutSimple class. I then attach the appender to the root logger with the log level set to DEBUG. Listing 2 contains a small script that makes use of this configuration file. For simplicity, you should save this code to a file in the same directory as the configuration file, since log4php looks for the configuration file in the same directory by default—more on this in a moment. The first thing that is done here is to include the LoggerManager which is responsible for providing logger instances that are defined by the configuration file. The LoggerManager operates as a singleton pattern, and the configuration is read only once per request. The last thing in this code is to get the root logger by calling LoggerManager::getRootLogger() and call its debug() function with a message. Running this code will give you this result: DEBUG - Hello World!
The most used functions for a logger are: • debug() - used for debug messages • info() - used in an informational context like “username logged in” or “user typed wrong password” • warn() - used when entering a potentially harmful situation • error() - used when something goes wrong, does not cause the application to terminate (non-fatal) • fatal() - used for really bad situations where execution must be aborted
25 • php|architect • Volume 4 Issue 12
The root logger will catch all logging events, so you may create other loggers for different logging purposes. Using the configuration in Listing 1 as a base, we can add another logger called “audit” for tracking audit messages and send them to a file with more information in the layout (see Listing 3). To get the new logger you can call LoggerManager::getLogger(‘audit’): $logger =& LoggerManager::getLogger(‘audit’); $logger->info(“User profile updated“);
Configuration By default, log4php will look for a configuration file called log4php.properties within the current directory and use the LoggerPropertyConfigurator to parse the property file which is in a simple INI-file format. The filename is set by the LOG4PHP_CONFIGURATION constant, and can be changed to whatever you’d like to name your configuration file (e.g. log4php.ini). You can also configure log4php through an XML based configuration file which tends to be a bit more readable than the simple key-value format. Listing 4 shows an example of the configuration from Listing 1 converted to an XML configuration. Using XML, you get a better feeling of what you’ve put into the different parts of your configuration. In order to enable the use of an XML based configuration file, you need to change that class log4php uses to parse a configuration file. This is done through the LOG4PHP_CONFIGURATOR_CLASS constant. Listing 5 shows a complete sample script for using the configuration from Listing 4. If you do not get log messages from log4php, you should check your setup once more. If log4php does not find a resource needed for appending a logging event, or your configuration is wrong, it will drop the logging event and continue execution. This way, it does not get in the way for your application. You should ensure that you get the expected messages when you configure log4php the first time to verify your setup. If you have problems with configuration you can always turn on internal debugging in log4php by adding log4php.debug = true to your configuration file. This will give more output about what log4php is actually doing. If your configuration is wrong you will get warning messages like this: Warning: LoggerDOMConfigurator::tagOpen() APPENDER cannot instantiate appender ‘default’
However, never leave this on by default; use it only for debugging.
Logging and Monitoring with log4php
Useful Appenders Log4php comes with a number of appenders to choose from. Figure 1 shows a list of all available appenders. The most typical appenders for operational purpose are LoggerAppenderDailyFile for logging events to files on daily basis and LoggerAppenderDb for logging events to a database, which gives you more flexibility in terms of statistics and extracting logging data later on. Listing 6 shows how to configure a daily file appender. Notice how you apply the date pattern through the datePattern parameter and use the conversion modifier %s to place the date in the filename provided in the file parameter. The LoggerAppenderDailyFile makes use of PHP’s date() function, so you can make it a weekly or monthly appender if you like. In a mission critical application, you need instant feedback from the application if something goes seriously wrong. Most people like to use an email appender for that. Listing 7 shows a minimal configuration for an email appender, setting from and to addresses, and a subject. The layout provides the body of the email message. You should not use the email appender to anything above log level FATAL or ERROR since it will most likely fill up your mailbox with more messages than you actually want.
Useful Layouts
FIGURE 1 APPENDER
DESCRIPTION
LoggerAppenderConsole LoggerAppenderDailyFile LoggerAppenderDb LoggerAppenderEcho LoggerAppenderFile LoggerAppenderMailEvent LoggerAppenderMail LoggerAppenderNull LoggerAppenderPhp LoggerAppenderRollingFile LoggerAppenderSocket
Append log events to STDOUT or STDERR Log events to a file per day Log events to a database table using PEAR::DB Log messages using echo Log events to a file One mail per event using mail() One mail per request using mail() Never output any message Raise event using trigger_error() Backing up log files when reach a defined size Serialize events and send them over a network socket Log event using syslog()
LoggerAppenderSyslog
FIGURE 2 LAYOUT
DESCRIPTION
LoggerLayoutHtml LoggerLayoutSimple
An HTML table with one row for each message. A very simple format with log level and the log message. A text layout containing time, thread, category and nested diagnostic context. A flexible layout based on a pattern string. An XML file with an ‘event’ element per logging event.
LoggerLayoutTTCC LoggerPatternLayout LoggerXmlLayout
You also have a range of layout possibilities. Figure 2 shows a list of available layouts. If you are logging to file, LoggerLayoutTTCC will be a good default layout. The layout is compiled like this: time [thread] LEVEL category - message
And here’s an actual example: Sat Nov 26 16:56:48 2005,121 [887] FATAL root - System crash!
Listing 8 contains an example of how you can tweak this layout to remove thread and microseconds and modify the date format. If you want logs available through your browser you should consider using the HTML layout and combine it with a daily file appender so you can download the log files in your browser. Listing 9 shows a suggested configuration for this. I’ve set the loglevel to WARN here because you probably only want to look for problems in this type of appender. I’ve also enabled locationInfo on this layout to add the line number and file in which an event occurred, to each message. The pattern layout is the most flexible of all the layouts. The result of this layout depends on a conversion pattern closely related to the sprintf() function added to the conversionPattern property of LoggerPatternLayout
26 • php|architect • Volume 4 Issue 12
FIGURE 3 CONVERSION SPECIFIER
DESCRIPTION
%c
Category
%C %d
Class name Date of logging event (may be followed by a date format specifier enclosed between braces which follows the PHP date() pattern. In example: %d{d M Y H:i:s}) Filename Location information (includes both %F and %L) Line number Your message Function name Platform-dependent newline (\n, \r\n etc.) Priority of the logging event Milliseconds elapsed since the start of the execution until the creation of the logging event Thread (process id) NDC (nested diagnostic context) associated with the thread MDC (mapped diagnostic context) associated with the thread. The conversion character must be followed by the key for the map placed between braces (for example: %X{referrer})
%F %l %L %m %M %n %p %r %t %x %X
Logging and Monitoring with log4php
LISTING 9 1 2 3 4 5 6 7
log4php.appender.default = LoggerAppenderDailyFile log4php.appender.default.datePattern = Ymd log4php.appender.default.file = /path/to/dailylog_%s.html log4php.appender.default.layout = LoggerLayoutHtml log4php.appender.default.layout.title = MyProduct Log log4php.appender.default.layout.locationInfo = true log4php.rootLogger = WARN, default
LISTING 10 1 log4php.appender.default = LoggerAppenderEcho 2 log4php.appender.default.layout = LoggerPatternLayout 3 log4php.appender.default.layout.conversionPattern=”%d{Y-m-d H:i:s} %-5p %c: %m in %F at %L%n” 4 log4php.rootLogger = DEBUG, default
LISTING 11 1 2 5 6 7 8 9 10 11 12 13 14 15 16
LISTING 12 1 ; This configuration need this table: 2 ; 3 ; CREATE TABLE log4php ( 4 ; `timestamp` varchar(32) default NULL, 5 ; `logger` varchar(32) default NULL, 6 ; `username` int(11) default NULL, 7 ; `ip_address` varchar(15) default NULL, 8 ; `uri` varchar(255) default NULL, 9 ; `level` varchar(32) default NULL, 10 ; `message` varchar(64) default NULL, 11 ; `thread` varchar(32) default NULL, 12 ; `file` varchar(64) default NULL, 13 ; `line` varchar(4) default NULL, 14 ; ) ENGINE=InnoDB; 15 ; 16 log4php.appender.mysql = LoggerAppenderDb 17 log4php.appender.mysql.dsn = mysql://root: password@localhost/log4php 18 log4php.appender.mysql.createTable = false 19 log4php.appender.mysql.table = log4php 20 log4php.appender.mysql.sql = INSERT INTO log4php (timestamp, logger, username, ip_address, uri, level, message, thread, file, line) VALUES (‘%d{Y-m-d H:i:s}’,’%c’, ‘%X{username}’,’%X{ip_address}’,,’%X{uri}’,’%p’,’%m’,’%t’,’%F ’,’%L’) 21 log4php.rootLogger = INFO, mysql
LISTING 13 1 2 3 4 5 6 7 8 9 10
27 • php|architect • Volume 4 Issue 12
(see Listing 10 for a complete configuration example). A conversion pattern is a string composed of conversion specifiers. A conversion specifier starts with a percentage character, an optional format modifier and a conversion character. For example, the conversion pattern “%d{Y-m-d H:i:s} %-5p %c: %m%n” will look like this: 2005-11-26 21:26:36 INFO root: User data updated ok
The first part is the date on a specified format; the second part is log level; the third part is the logger category; the fourth part is your message; and the last part is the platform dependent newline character(s). Figure 3 shows a complete list of conversion specifiers that can be used in a pattern layout. By default, the result of all conversion specifiers is rendered as-is. However, it is possible to use a format modifier between the percent sign and the conversion character to change the minimum field width, the maximum field width and text justification within a field. Use a positive integer to specify the minimum field width, a period followed by a positive integer to specify maximum field width. Left-justify within a field by using the minus sign (-), or else it will be justified right. Here are a few examples: • %-5p – left-justify the priority within 5 characters • %10c – right-justify the category if within 10 characters • %10.15c – right-justify the category if shorter than 10 characters; if category is longer than 15 characters, truncate from the beginning
Adding Additional Filters By using filters, you can fine tune which messages you want to receive from an appender, by specifying matching log levels or strings that you want to drop. This is particularly useful when you need to attach appenders to a logger with a different kind of log level than you really want messages from. For example, if you want to attach an email appender to only get notifications about fatal events, but the log level of the logger is set to something else. Figure 4 contains a list of available filters. Listing 11 shows the configuration of an appender which drops debug events using a LoggerLevelMatchFilter with the parameter levelToMatch set to DEBUG and acceptOnMatch to false. Note that filters are only supported in XML based configuration and not in the simple property configuration. You can add as many filter rules as you like to a given appender.
Logging and Monitoring with log4php
Debugging Objects You can pass any PHP object to any of the logging functions in a logger, and it will be rendered through the log4php’s object rendering mechanism. This is a useful feature when debugging instead of using print_r() or var_dump() which will output result to screen. The default implementation of object rendering uses var_export() to return the string representation of the instance you apply. Be careful with large objects and recursion. You should only use it for your own business objects or similar.
FIGURE 4 FILTER
DESCRIPTION
LoggerLevelMatchFilter
Filtering based on a level to match in the LevelToMatch property. Set AcceptOnMatch to true or false whether or not you will accept the logging event. Used to reject messages with levels without a certain range based on the LevelMin and LevelMax property. Set AcceptOnMatch to true or false whether or not you will accept the logging event within the range. Filtering based on a string to match in the StringToMatch property. Set AcceptOnMatch to true or false whether or not you will accept the logging event. Drop all logging events.
LoggerLevelRangeFilter
LoggerStringMatchFilter
Diagnostic Context In a web application, you are serving multiple clients simultaneously and you probably want to add more information to your logging events about the request, such as user identification: IP address and/or the current request URI. Mapped Diagnostic Context, or MDC for short, is a technique used to distinguish between interleaved log messages. MDC properties can be set statically anywhere before using the logger using LoggerMDC::put(‘key’, $value). LoggerMDC::put(‘remote_addr’, $_SERVER[‘REMOTE_ADDR’]);
To map MDC properties in your layout, you should use the pattern conversion character “X” followed by the key, between braces. For example, %X{remote_addr}. log4php.appender.Test.layout = LoggerPatternLayout log4php.appender.Test.layout.conversionPattern = “%d{Y-m-d H:i:s} %-5p %c: %X{remote_addr} %m%n”
Listing 12 shows how to use LoggerAppenderDb to setup an MDC configuration to append logging events to a MySQL database. You need to create the table that you want to append to, yourself, in order to have fields ready for MDC properties. For that reason I’ve also disabled the createTable property on the database appender. The sql property of the database appender can include every conversion specifier from the pattern layout. Listing 13 contains a sample script using MDC to map additional context data into all logging events.
Dealing with Errors When it comes to monitoring, you want to know about all kinds of errors that are happening during runtime, in a controlled manner. It is never a good idea to display error messages to the end user when something goes wrong. Not only does it look bad, but it can also give potential hackers valuable information. You should store all error messages and hide them from the end user. The simplest way to fix this is to turn off display_errors and turn on log_errors in php.ini to log them to a file defined by error_log, but a better way is to let log4php 28 • php|architect • Volume 4 Issue 12
LoggerDenyAllFilter
LISTING 14 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
Logging and Monitoring with log4php take care of the messages. PHP contains both normal PHP errors and exceptions (as of PHP5). Listing 14 shows an example of how to implement a custom error and exception handler for uncaught errors. I’ve implemented a LoggerHandlers class with a static function errorHandler() which appends log messages, based on the error code, when an error occurs. This implementation is a bit strict in terms of log levels, but I find it more useful to be strict during development and loosen up a bit later on if necessary. I’ve
that you get the messages and not the end user.
What about Performance? Logging comes at a cost, and appending logger messages to various destinations will slow down your application. There are, however, some guidelines to be aware of when using log4php that can help you minimize unnecessary overhead. First of all, you should use the right log levels in the right situations. For example, you should distinguish
When it comes to monitoring, you want to know about all kinds of errors that are happening during runtime, in a controlled manner. also implemented a static function exceptionHandler() to use as the exception handler. Both are initialized in the constructor with set_error_handler() and set_exception_handler(). This is a nice way of ensuring FIGURE 5 ����������
����������
������
������
���������� ����
���������� ����
����� �������� ������ ���������� ����
����������
����������
������
������
���������� ���������
���������� ���������
29 • php|architect • Volume 4 Issue 12
between fatal and error as well as info and warn levels and what they mean in your application. Then you will get the most valuable output from your configuration. Second, it is a good practice to test if the log level is enabled on a logger before logging. Testing is pretty fast compared to the operation you will be doing with appending a message to a logger. You can always use the functions isDebugEnabled() and isEnabledFor() for this purpose. if($logger->isDebugEnabled()) { $a = 1; $logger->debug(“\$a is $a”); $a++; $logger->debug(“\$a is $a”); }
Unlike Java and log4j, where the configuration is read upon startup of the process, PHP will initialize log4php on each request. To improve startup time, you can serialize the LoggerHierarchy instance to a file. A LoggerHierarchy is the structure that contains all loggers that were created, based on your configuration. By caching this structure, you do not have to rebuild it upon each request. Listing 15 provides a simple example of implementing this concept, first by naming a cache filename and checking if this file already exists. If not, get the reference to the current LoggerHierarchy instance
Logging and Monitoring with log4php through LoggerManager::getLoggerRepository() and store a serialized version of it in the cache file. You will get the logger hierarchy back when unserializing the cache. Then you will get access to all loggers by calling getLogger(‘name’) or in this case getRootLogger() on the logger hierarchy instance. To create more reusable code for this concept you could create your own cached logger configurator extending the LoggerPropertyConfigurator or LoggerDOMConfigurator and override the configure() function to do the caching. The performance gain of caching the logger hierarchy will increase the more configuration you add to your setup. In general, if performance is really critical to you it may be a good idea to minimize the use of appenders and only log one place, for example to a database. Then set up a cron job to do some post processing like looking for fatal errors and sending them by email, and creating your own script to search for messages in the database instead of using the HTML appender with the daily file appender described earlier.
Create Your Own Logging Server Log4php can be used extensively in a distributed environment with many applications. Whether you want to optimize performance overhead or gather logging messages in one central place, you can create a logging server to send logging events to and let the logging server do the actual logging for you. Figure 5 illustrates the centralization of logging from having all logging configuration set up in each application to have that configuration on a logging server. With this approach, each application then only has to define one appender which logs directly to the logging server. If you are familiar with log4j you have probably used or heard of Chainsaw, a utility to read and receive logging events from log4j using an XML layout. You could use Chainsaw as your logging server, but I prefer not to use the XML layout and write my own implementation just to show you how easy it can be done. Listing 16 contains an application logging configuration using a socket appender. This is the only appender needed for an application since the logging server should do the rest of the work. A socket appender takes three parameters. The first parameter remoteHost contains the protocol combined with the host name or IP address. The second parameter port is which port the socket appender should connect to on the remote host. And the third parameter useXml will generate payload using the XML layout if set to true. In my example I have set this to false which will serialize the LoggerLoggingEvent object instead and is way faster to 30 • php|architect • Volume 4 Issue 12
work with than parsing XML data. Listing 17 is our sample application that contains a few logging events that will be using the socket appender in Listing 16. Listing 18 will be the logging server’s configuration which logs to screen and to a file. Listing 19 contains code for a logging server using the PEAR::Net_Server package. PEAR::Net_Server is a generic server interface to create daemon scripts based on the socket extension. You can choose from two different server drivers: sequential and fork. I have chosen the fork driver where a new process is forked for each client that connects to the server. This driver requires the pcntl extension which enables process control support on *nix based platforms. If you are on Windows or can’t enable process control support on your installation, you can always use the sequential driver. Net_Server has an object oriented approach and you need to create a handler class that extends LISTING 15 1 2 3 4 5 6 7 8 9 10 11 12 13
LISTING 16 1 2 3 4 5 6
; application.properties log4php.appender.default = LoggerAppenderSocket log4php.appender.default.remoteHost = tcp://127.0.0.1 log4php.appender.default.port = 9090 log4php.appender.default.useXml = false log4php.rootLogger = INFO, default
LISTING 17 1 2 3 4 5 6