AUGUST 2003
VOLUME II - ISSUE 8
The Magazine For PHP Professionals
Maintenance from the Outset Building a monitoring system into your scripts
An Introduction to Ming Create amazing Flash applications on-the-fly
An Introduction to cURL Discover the ins and outs of this indispensable tool
Transforming XML to PDF with the help of LaTeX Embedding Assembler in PHP
Case studies in Client/Server Applications
www.phparch.com
Take your PHP to new lows
Get Ready For php | Cruise See inside for details
March 1st - March 5th 2004
Plus: Tips & Tricks, Product Reviews and much more...
This copy is registered to: Liwei Cui
[email protected]
! W E N
Existing subscribers can upgrade to the Print edition and save! Login to your account for more details.
Buy now and save $10 off the price of any subscription† Visit: http://www.phparch.com/print for more information or to subscribe online.
php|architect Subscription Dept. P.O. Box 3342 Markham, ON L3R 9Z4 Canada Name: Address: City: State/Province: ZIP/Postal Code: Country:
php|architect The Magazine For PHP Professionals
Your charge will appear under the name “Marco Tabini & Associates, Inc.” The first issue of your subscription will be mailed to you in September, 2003. Please allow up to 6 weeks for your subscription to be established. *US Pricing is approximate and for illustration purposes only.
Choose a Subscription type: Canada/USA $ 81.59 $67.99 CAD ($59.99 $49.99 US*) International Surface $108.99 $94.99 CAD ($79.99 $69.99 US*) International Air $122.99 $108.99 CAD ($89.99 $79.99 US*)
Payment type: VISA
Mastercard
Credit Card Number: Expiration Date: E-mail address: Phone Number:
American Express
Signature:
Date:
*By signing this order form, you agree that we will charge your account in Canadian dollars for the “CAD” amounts indicated above. Because of fluctuations in the exchange rates, the actual amount charged in your currency on your credit card statement may vary slightly. †Limited time offer expires August 31st, 2003.
To subscribe via snail mail - please detach this form, fill it out and mail to the address above or fax to +1-416-630-5057
We’ve got you covered, from port to sockets.
?>
php | Cruise
Port Canaveral • Coco Cay • Nassau
March 1st - March 5th 2004 Signup now and save $100.00! Hurry, space is limited.
Visit us at www.phparch.com/cruise for more details. Andrei Zmievski - Andrei's Regex Clinic, James Cox - XML for the Masses, Wez Furlong - Extending PHP, Stuart Herbert - Safe and Advanced Error Handling in PHP5, Peter James - mod_rewrite: From Zero to Hero, George Schlossnagle - Profiling PHP, Ilia Alshanetsky - Programming Web Services, John Coggeshall - Mastering PDFLib, Jason Sweat - Data Caching Techniques Plus: Stream socket programming, debugging techniques, writing high-performance code, data mining, PHP 101, safe and advanced error handling in PHP5, programming smarty, and much, much more!
TABLE OF CONTENTS
php|architect Departments
Features
5
10
Editorial
Transforming XML to PDF with LaTeX Stephan Schmidt
I N D E X
7
What’s New! 16
26
Ming & PHP Seth Wilson
Granted! Announcing the winners of the 2003 php|architect Grant Program
33 29
Sockets: Part 2 Eugene Otto
Datanamic DeZign for Databases 3 Peter James
65
Tips & Tricks
41
Peter James
John W. Holmes
69
Bits & Pieces
Grokking cURL
48
Maintenance from The Outset Graeme Foster
71
exit(0); Expect More From php|a Marco Tabini
57
Embedding Assembler in PHP Igor Gorelik
August 2003 · PHP Architect · www.phparch.com
4
EDITORIAL
E D I T O R I A L
R A N T S
I
could not be happier right now. As I sit here at my laptop, writing this editorial, by my side sits a glass of beer that I made from scratch, not quite a month ago. I’m inspired by the similarities that exist between the evolution of php|architect as a powerhouse publication and de facto standard source of PHP knowledge, and the creation of what is now a simple glass of lovely ale. I brew with real grain and real hops. None of this ‘just add water’ stuff. I’ve never done it, and I won’t. I suppose it’s fine if you just want a ‘quick and dirty’ beer. However, I have an overwhelming feeling that my integrity as a brewer would be sacrificed for ‘ease’ (I call it ‘laziness’), and it doesn’t sit well with me. I want honest beer. I want to know how the wort was produced, and where it came from. Indeed, I want to be intimately associated with the very essence of the finished product. For better, or for worse. I found this same spirit in the publisher of php|architect, who I’ve now come to know as both a good friend and close business associate, Marco Tabini. Marco understood my leanings toward things that were, for lack of a better term, ‘organic’ or ‘untainted’. I wanted material written by doers. I shunned several articles by professional writers whose writing lacked the spirit that we’ve by now become accustomed to at php|architect; the spirit of someone who has fought and lost battles on their way to great discoveries using PHP. Other writers, in comparison, appear to have ‘just added water’. We’ve made it a goal to avoid such articles. Of course, it is the ‘road less traveled’, but we feel it has made all the difference, and you’ve let us know that we’re right. Of course, brewing from scratch comes with its headaches. Brewing from scratch means more steps are involved, the materials are handled and transferred more often, and more opportunities exist for molds and bacterias to be introduced into the beer. While I’ve so far been able to avoid that, and produce very drinkable beer, it is not without its flaws. The one I’m drinking now took on a bit of a ‘chill haze’ after being bottled, for example; a minor problem that doesn’t affect anything but the aesthetic qualities of the beer. As I continue to brew and master the craft, I’ll inevitably fix the ‘chill haze’ problem, while introducing others. Eventually, it all works out, and you achieve ‘excellent beer’.
August 2003 · PHP Architect · www.phparch.com
php|architect Volume II - Issue 8 August, 2003
Publisher Marco Tabini
Editor-in-Chief Brian K. Jones
[email protected]
Editorial Team Arbi Arzoumani Brian Jones Eddie Peloke Peter James Marco Tabini
Graphics & Layout Arbi Arzoumani, William Martin
Managing Editor Emanuela Corso
Authors Greame Foster, Igor Gorelik, Peter James, Eugene Otto, Stephan Schmidt, Marco Tabini, Seth Wilson php|architect (ISSN 1705-1142) is published twelve times a year by Marco Tabini & Associates, Inc., P.O. Box. 3342, Markham, ON L3R 6G6, 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.
Contact Information: General mailbox: Editorial: Subscriptions: Sales & advertising: Technical support:
[email protected] [email protected] [email protected] [email protected] [email protected]
Copyright © 2002-2003 Marco Tabini & Associates, Inc. — All Rights Reserved
5
EDITORIAL Such is the case with this magazine as well. We’re not perfect. We haven’t ever claimed to be perfect, and as a team, I don’t think any of us could ever foresee a day when there isn’t at least the slightest bit of ‘haze’. However, it is the attitude of this team that as long as there is not perfection, there is work to be done, and so we’re constantly busy, if not with fingers to the keyboard, then with developing ideas or analyzing weaknesses. It is this attitude, and the fact that it permeates every digital thread of the publication, that will earn us whatever critical acclaim may await us. Finally, beer and brewing is a learning process. I was fortunate enough that my buddy Matt was an already experienced brewer without a convenient location to brew, and I was a homeowner with a large back yard and a willingness to learn. Matt and I have known each other since childhood, and we work well together. We understand each other and relate to each other better than most other people we know. This makes for a great working relationship, partially because we grew out of the ‘polite friendship’ stage somewhere around age 14. We constantly question and debate. Our beer thus far has been better for it. This type of working relationship is invaluable in creating anything of any quality. In striving for quality of material in php|architect, anyone who has written for our editorial team will note that I take a decidedly different approach. The approach is largely the brainchild of Marco and I, who have somehow been able to achieve a vigorous and successful working relationship. Constantly questioning and debating each other, we’re always playing ‘devil’s advocate’ to insure that, in the end, we’ve done right by all parties involved. In the process we’ve learned what makes this monster we’ve created tick, and how to channel our efforts to add even more value to our readership. Our ability to come to a ‘meeting of the minds’ on all aspects of the publication has brought us not only to where we are now, but will influence the direction of the publication in the future – for better, or for worse. In reality, at the end of the day, it’s our ability to tell each other that our ideas completely suck that has made all the difference! But there is another ingredient in our recipe at php|architect. It’s the ‘recognition’ phase. Recognition of a person’s ‘highest and best use’.
August 2003 · PHP Architect · www.phparch.com
We seek people to join our team who we feel we can work with, and we eventually move everyone into a position where their interests and skills are put to work to produce the best ‘stuff’ that they can. As I’ve mentioned from the very beginning, editorial work is not my highest and best use. No matter how much I love editing and the editorial process, the reality is that a) I’m not a hardcore coder, and b) I’m not a hardcore editor! It is with these words that I am proud to pass the Editor in Chief torch to the very capable Peter James. Peter has accomplished much in his relatively short time as Senior Editor at php|architect, affecting both the outward appearance of the magazine, as well as the backend procedures for coordinating the editorial process. All by his own initiative, and all without a thought for anything but the good of the publication. Furthermore, as if this weren’t enough, he still constantly asks for more responsibility, and a greater role in the magazine’s creation. Finally, I came to the conclusion that the only way for Pete to do anything more was to take my job, which would allow me to be applied to my highest and best use as well. So from now on, it will be Pete’s job to try hard to give you all a reason to read the editorial column that I’m still convinced is a pretentious waste of paper (be it digital or ‘dead tree’ in form). I’ll not be far, mind you! I’m taking on a new role that will also produce some really cool and exciting new (but as yet unmentionable) ‘stuff’ in the not too distant future. I’m excited at the opportunity, though I’ll always make myself available on those sleepless nights when Pete can’t get some ‘damned Linux code’ to validate or something. I wish Pete a lot of luck, and thank him for the hard work he’s done, along with the rest of the production crew who have helped php|architect become what we are today, and what we will be tomorrow. I also thank all of the readers for the feedback and guidance along the way. ...until we meet again... brian.
6
3 php|Cruise ‘04 10 Transforming XML to PDF with LaTeX 16 Ming & PHP 33 Sockets: Part 2 41 Grokking cURL 48 Maintenance from The Outset 57 Embedding Assembler in PHP 7 What’s New! 26 Granted! 29 Datanamic DeZign for Databases 3 65 Tips & Tricks 69 Bits & Pieces 71 e x i t ( 0 ) ;
What’s New! S T U F F
PHP 4.3.3 RC3 PHP.net announced the release of PHP 4.3.2 release candidate 3. “This should be the last release candidate prior to the final 4.3.3 release. Please test this release as much as possible, so that any remaining issues can be uncovered and resolved.” Bug fixes include: • escapeshellcmd() can now handle quoted arguments • exit code lost when exit() called from register_shutdown_function() • methods misidentified as constructors • and much more.....
N E W
Visit php.net to download or view the change log.
Apache 2.0.47 The Apache Software Foundation and the Apache HTTP Server Project announce the tenth public release of the Apache 2.0 HTTP Server. This is a security, bug fix and minor upgrade release. Due to security issues, any sites using Apache 2 versions prior to Apache 2.0.47 should upgrade to Apache 2.0.47. Security issues: a. Fixed a bug in the handling of accept errors by the prefork MPM when handling accept errors, which could allow a denial of service attack if multiple listeners are configured. b. Fixed a bug in the optional renegotiation code in mod_ssl which could cause cipher suite restrictions to be ignored if optional renegotiation is enabled.
August 2003 · PHP Architect · www.phparch.com
c. Fix of denial of service attack in mod_proxy’s handling of DNS results. New features: Added support for a “prefer-language” environment variable to mod_negotiation” To download, visit Apache.org
ionCube and the Cerberus Encoder ionCube.com announced the release of the ionCube 3.0. The ionCube standalone PHP encoder is a high performance encoding solution for PHP, offering encoding of compiled code to deliver the maximum security and run-time performance for encoded file, and features to allow easy integration into build and release systems, and also websites for just-in-time software delivery. New Version 3.0 advantages: a. Customizing of Loader event messages b. Restricting encoded files to only cooperate with other encoded files that have certain ‘properties’. c. Improved encoding performance. In addition to Version 3.0, Ioncube has released the Cerberus Encoder. This is the same as the full featured encoder, but adds the ability to restrict files to a MAC address. For more information, visit ionCube.com.
7
NEW STUFF
Databases MySQL and PostgreSQL both announced new releases this month.
PHPNuke 6.7 PHPNuke.org announced the release of version 6.7. PHPNuke is a PHP based content management system. This newest version fixes XSS and other vulnerabilities and security bugs like path disclosure, and adds associated topics to the News module. There are many cosmetic changes and minor bugs fixed, and the Update folder was reorganized. You can get more information or download from PHPNuke.org.
PostgreSQL 7.3.4 PostgreSQL.org announced the release of their latest version 7.3.4. This version addressed a potentially serious (although rare) server startup failure that was recently reported. This release is critical for users of PostgreSQL version 7.3.3, and highly recommended for all other PostgreSQL users. The latest version can be downloaded from the PostgreSQL FTP site.
PHPEdit 0.7.1.131 PHPEdit.net announced the release of PHPEdit 0.7.1.131. It’s still a development version but takes one step further to the stable release. It contains proposal #2, #3 and #7 of the PHPEdit Community. PHPEdit invites all users to report problems or tweaks in 0.7.1.131 to the PHPEdit Community. PHPEdit also asks all users to vote for requests they want to be in the next release to the Community. Visit PHPEdit.net to download.
patUser 2.1.2 and 2.20 Beta PHP Application Tools announced the release of patUser 2.1.2 and 2.20 Beta New in this version: bugs, notices and warnings removed. Warning: this is the last version of patUser supporting patDbc, as we are switching to PEAR::DB. Version 2.2.0 is the new branch supporting PEAR:DB - but keep in mind that it is still BETA! If you are willing to test it, feel free to send your bug reports to gERD directly. “ For more information or to download, visit PHP-Tools.de.
MySQL 4.0.14 MySQL.com announces the release of version 4.0.14. This release is a maintenance release for the current production version and includes functionality such as: a. Enabled `INSERT’ from `SELECT’ when the table into which the records are inserted is also a table listed in the `SELECT’. b. Changed optimiser slightly to prefer index lookups over full table scans. c. `FLUSH LOGS’ now rotates relay logs in addition to the other types of logs it already rotated. For the full list of additions and bug fixes, or to download, visit MySQL.com.
php|a August 2003 · PHP Architect · www.phparch.com
8
Transforming XML to PDF with LaTeX
F E A T U R E
By Stephan Schmidt
Types of documents XML is commonly used to store several types of information. Not only do developers use it in their daily work to store configurations or define protocols, it is also a great way to structure your everyday documents, like letters, books, or articles. Even if they can be read with any text editor (XML is an ASCII format), XML documents are not the first choice when it comes to documents that you'd like to read in your free time. The mixture of tags, attributes and plain text may confuse you while you are trying to get to the actual information. XML was designed to be read by machines, not humans. XML does not contain any layout but only the structure of the raw data or content. An XML document consists of several tags (like HTML) which describe the information contained within the tags. Any program may use these tags to decide what to do with the text information between these tags. This could mean a program is able to prioritize parts of a document, if it had to create a search index or something similar. Humans tend to decide what to do with the information they read according to how it has been laid out. If you are reading an article, you will surely recognize that the headline has some significance in the context of the rest of the article, as it is printed in bold and large letters.
August 2003 · PHP Architect · www.phparch.com
So if information has to be accessed by computers and humans, the best solution would be to present both species the same information in different formats. The first choice of delivering information to a machine is nowadays XML, while PDF can be read with almost every client and operating system, and can easily be printed, which enables you to take the information to wherever you'd like without the need for a computer. As we're living in the age of automation, you will not want to create both versions of the information on your own but use your friendly neighbourhood webserver and scripting language (PHP) for it. This article will explain one way to achieve this without a single drop of sweat
Transforming documents To automate the generation of one version of the two required documents, you have to define which version should be created manually and which one should be generated by an application. This problem is solved in the blink of an eye, as the previous section explained REQUIREMENTS PHP Version: 4.x and above O/S: Linux Additional Software: LaTex Code Directory: N/A
10
FEATURES
Transforming XML to PDF with LaTeX
that XML can be read by machines, and if your webserver does not differ too much from the one we used, it definitely is a machine. So once we have made the decision to transform the XML document into a PDF document, we have to stop for a moment and think about how this transformation can be achieved. After choosing PHP as a programming language (as this is a article about PHP, after all) we realize that the task of distributing and accessing the document is now simpler, as both versions can later be accessed from anywhere in the world from our web server over the internet. If you are familiar with transforming XML documents you will probably be sitting at your desk yelling "Why the $@#! aren't you using XSLT? It was designed specifically for the task of transforming XML documents!". The answer is quite simple: XSLT was designed to transform an XML document to another XML document with a different structure. A PDF is not an XML application, and it is not even based on the ASCII standard. This means you will have a hard time transforming XML to PDF using XSLT. Some of you may still be sitting there wondering why we are not using XSL-FO, which is able to create PDF files and uses XML as its input format. We do not want to use XSL-FO as it is not very easy to use. Furthermore, it has already been discussed in other articles, and we are here to teach you something new. Call it an 'alternative method'. A better way to transform XML to PDF is to take a detour and use LaTeX. It is based on the ASCII character set, available on nearly every operating system for free and can easily be converted to PDF documents (or other printable formats).
LaTeX is based on Donald E.Knuth's TeX typesetting language. Development started in 1985 by Leslie Lamport and is currently maintained by the LaTeX3 Project . A typical LaTeX document looks like this
A short introduction to LaTeX
As there are a lot of tutorials on LaTeX available on the web (see the end of this article for useful links), we will only list the most important features and show some simple examples. LaTeX provides features for:
LaTeX is a document preparation system for high-quality typesetting. It is most often used for medium-tolarge technical or scientific documents, but it can be used for almost any form of publishing. LaTeX is not a word processor! Instead, LaTeX encourages authors not to worry too much about the appearance of their documents, but to concentrate on getting the right content. This means LaTeX is not edited in a WYSIWYG (What You See Is What You Get) editor. LaTeX documents can be created using your favourite editor, whether it is vi, emacs, Homesite or even Notepad (but not FrontPage). This also means that LaTeX documents can be created by any application that is able to create text files (and of course PHP is able to do this). If you want to view a LaTeX document, it has to be converted first. You cannot view it directly in your editor, or you will get the plain source code of your document, not the version including layout. This would be similar to trying to view an HTML document in vi or Notepad.
August 2003 · PHP Architect · www.phparch.com
\documentclass{article} \title{Dynamic transformations of XML to PDF with LaTex} \author{Stephan Schmidt} \date{April 2003} \begin{document} \maketitle We love XML, but everyone wants PDF. \end{document}
If you are familiar with markup or programming languages, you may already have guessed that this means: 1. The document is an article. 2. The title of the document is "Dynamic transformations of XML to PDF with LaTeX". 3. The article has been written by Stephan Schmidt ("Hey, that's me! Look mum, I'm on TV!") 4. It has been written in April 2003. 5. The document consists of a title, followed by the text "We love XML, but everyone wants PDF."
• Typesetting articles, technical reports, letters, books and slide presentations • Control over large (and we really mean large) documents • Control over sectioning, cross references, footnote, tables and figures • Automatic creation of bibliographies and indexes • Inclusion of images • Using PostScript or Metafont fonts
Basic usage of LaTeX LaTeX documents consist of commands, macros and environments as well as plain text. Commands always start with a backslash ("\"). If a command needs parameters, they have to be enclosed in curly braces ("{" and "}"), and if those parameters are optional, they have to
11
FEATURES be enclosed in brackets ("[" and "]") and separated with commas (","). Typical LaTeX commands look like this: \maketitle \footnote{I am a footnote} \documentclass[a4paper,twoside]{book}
It is possible to include comments in your documents, they have to start with "%" and end at the end of the line (like "//" in PHP). \documentclass{article} % This will be an article % This line is a comment and will be ignored later
Transforming XML to PDF with LaTeX • \label, \bibitem, \ref and \href to create cross-references • \includegraphics to include images • \begin{table}, \begin{itemize} to create commonly used environments • \tableofcontents, \listoftables and \listoffigures to create indexes • ... and many more So your first LaTeX document could look like this: \documentclass[a4paper,twocolumn]{article} \usepackage{german} % support for German umlaut \usepackage{hyperref} \title{Me and the superheroes} \author{Me, of course} \date{\today}
Environments are used to split the document into logical parts (like tags split XML documents). Environments always start with the "\begin" command and end with the "\end" command. \begin{document} % Place anything that is part of the document here \end{document}
Now you know enough to start creating your very own LaTeX document, and we will guide you through the needed steps. Each document has to start with the "\documentclass" command, which is being used to define what kind of document you are creating. The documentclass is responsible for the command set that is available in your document. E.g. if you are writing an article, there is no use for the "\chapter" command. Furthermore the "\documentclass" command is used to define the basic layout style, like two column layout or paper format. After this command you may load packages containing macro definitions using the "\usepackage" command. Packages include features for localization (different character sets), hyphenation, graphics or cross references. Usually you will now include meta information about the document, like the title, author or the date it has been created. Finally the "\begin{document}" command is used to indicate the start of the actual document. Within the "document" environment you may use any LaTeX command that is used to structure the document or include graphics. Common commands include: • \section, \subsection and \subsubsection to structure the document • \em to emphasize parts of the document • \item to create lists • \footnote (for footnotes, of course)
August 2003 · PHP Architect · www.phparch.com
\begin{document} \maketitle \tableofcontents \section{My relationship to Superman} \subsection{How it started} When I was twelve, Superman was my greatest hero. \subsection{Our relationship grew stronger} I first met him in person at the age of 16. \subsection{Everything has to end} When he died at the hands of {\em Doomsday}, I was really sad and devoted my life to Batman. \section{My relationship to Batman} My relationship to Batman started last week so there's not much to tell, yet. But I already know some of his friends: \begin{itemize} \item{\em Robin}, the Boy Wonder \item{\em Oracle}, the former Batgirl \end{itemize} \end{document}
OK, you have created your first LaTeX document, containing the tragedy of your life, but nobody is able to read it. What you probably want to do is to create a PDF document from it, print it and distribute it through your local comic book store. The next section will show you how.
Converting LaTeX to PDF Converting LaTeX documents to PDF is not something you do every day, but nevertheless it is quite easy, assuming you have the right software installed. Otherwise you have to install it first. If you are using Linux, there is no problem at all. LaTeX is included in most distributions, and, if not, it is available as an installable package for your favourite package manager. The package is most often called "tetex". You will find more information on the teTeX homepage. If you are a Windows user, you should download and install MikTeX. This should pose no problem, as MikTeX is distributed as a Windows installer. Now, as you are proud to have LaTeX installed on your system, there is nothing to hold you back from generating the PDF version of your document. All you
12
FEATURES
Transforming XML to PDF with LaTeX
need to do is to call pdflatex with the filename of your LaTeX document: pdflatex superheroes.tex
This creates several files in the folder where you have saved your LaTeX document: • superheroes.pdf is the PDF file you wanted to create • superheroes.log is a log file containing all log messages • superheroes.toc contains the table of contents • superheroes.out contains bookmarks for the PDF reader • superheroes.aux contains all data needed for cross references If you execute pdflatex, the application parses the LaTeX document from top to bottom and generates the table of contents, anchor files for hyperlinks, PDF bookmarks and other meta information. As this data should also be included in the PDF file, you have to call "pdflatex superheroes.tex" twice to achive the desired result. If you have done this, open the file superheroes.pdf with Acrobat Reader and you should see your lifestory rendered as a PDF document. If everything went like it should, you will probably see something like what’s shown in Figure 1. To get acquainted with LaTeX, visit a tutorial on the web and try fooling around with various LaTeX commands and how they are rendered as PDF.
ments consist of plain text, you may use any PHP function to modfiy them. The easiest way is to dynamically create a LaTeX document using "echo" statements like it has been done with HTML for ages, capture the result and transform it to PDF. The following snippet shows how it is done: \documentclass{article} \begin{document} Hi, my name is . \end{document}
Now open your favourite webbrowser (Mozilla, I hope), open the script and append your name as GET parameter "name". The URL will probably look like http://localhost/latex.php?name=Aquaman. If you view the sourcecode of the resulting page, you should see the complete LaTeX source including the name you entered in the URL. Save it to disk and transform it with pdflatex.
Figure 1: Your first PDF
Dynamic creation of LaTeX and PDF documents You may wonder why this article has been published in a magazine about PHP, and there has not been any PHP code so far. This will change with this section, where you will learn to generate LaTeX documents dynamically. As LaTeX docuAugust 2003 · PHP Architect · www.phparch.com
13
FEATURES Of course this is not what we initially wanted to do, as you still have to save the LaTeX document and transform it manually. To automate the process of saving the file to disk you should use PHP's output control and file system functions. To automate the "pdflatex" call you can use "system" or "exec". These functions are used to execute any command that is available on your server, just like you would execute it in the shell. Using "echo" to dynamically create LaTeX documents is the easiest - but also the ugliest - way to complete the task. If you are using LaTeX to create a printable invoice for customers in your online shop, you have to mix LaTeX (which contains content like your address) with PHP code (which is responsible for the logic of the invoice). The resulting files will be very hard to maintain, if changes to either the logic our the layout have to be made. This means you should use the same tools that are nowadays commonly used for creating dynamic HTML pages. Correct, we are talking of template engines. Just use your favourite template engine (of course, we recommend ours, patTemplate), create your LaTeX templates and let PHP fill them with any content you can to find. You may retrieve content from the user by supplying forms, get it from databases, or even XML files, which is the topic of the next section.
Transforming XML documents using patXMLRenderer As the title of this article is "Transforming XML to PDF with the help of LaTeX" we are now going back to where we came from and take a look at XML documents and how they can be transformed to PDF. In order to know how to transform LaTeX to XML we have to take a look at the gap between XML and LaTeX documents. Both languages split the document into logical expressions that can be nested as deep as you like. So the easiest way to transform XML to LaTeX is to define a LaTeX representation for each XML tag that is used in your documents and parse the XML document recursively. The result will be a LaTeX document with the same structure and content as the source XML document. As we do not wish to reinvent the wheel, we are relying on an existing software package called patXMLRenderer. There is online documentation for this available at the official website. This application makes use of a templateing engine (patTemplate) and allows you to define a representation for each tag by creating a template with the same name as the XML tag. In this template, you may access the content of the tag and all of its attributes. As the document is parsed recursively using a SAX based parser, all children of a tag are transformed before the tag itself is transformed. patXMLRenderer also allows you to include dynamic content by overloading namespaces with methods of PHP classes. This enables you to
August 2003 · PHP Architect · www.phparch.com
Transforming XML to PDF with LaTeX include content (SOAP requests, database content, text files,...) while the XML file is being transformed to the LaTeX document. A sample XML file, that can be transformed to LaTeX could look like this: <article title="Me and the superheroes, part 2"> <paragraph title="I lied to you"> When I was talking about
Superman, I lied. He came back from the dead and rose to the glory he once had.
To transform this document to LaTeX (and later to PDF) you have to define representations for all tags used in the document: article, paragraph and imp. The LaTeX templates could look like this: <patTemplate:tmpl name="article"> \documentclass[a4paper,twocolumn]{article} \usepackage{german} % support for German umlaut \usepackage{hyperref} \title{<{TITLE}>} \author{Me, of course} \date{\today} \begin{document} \tableofcontents <{CONTENT}> \end{document} <patTemplate name="paragraph"> \section{<{TITLE}>} <{CONTENT}>\\ <patTemplate:tmpl name="imp" whitespace="trim"> {\em <{CONTENT}>}
The resulting LaTeX template will be: \documentclass[a4paper,twocolumn]{article} \usepackage{german} % support for German umlaut \usepackage{hyperref} \title{Me and the superheroes, part 2} \author{Me, of course} \date{\today} \begin{document} \tableofcontents \section{I lied to you} When I was talking about {\em Superman}, I lied. He came back from the dead and rose to the glory he once had.\\ \end{document}
Now all you have to do is to call "pdflatex" and the XML file has been transformed to PDF and, as prom-
14
FEATURES ised, you did not lose one drop of sweat. We are currently developing an application that automates the process of transforming XML to PDF and allows you to combine several XML files through a graphical user interface. It will hopefully be available as a download together with patXMLRenderer from our website .
Common pitfalls When transforming XML to PDF you have to be cautious and avoid some common pitfalls. First of all, you have to tell patTemplate that you want to create LaTeX files instead of HTML files. This is done when creating a new instance of the class, by passing "tex" as first parameter to the constructor. When creating LaTeX output, variables have to be enclosed in "<{" and "}>" instead of just "{" and "}" so they cannot be mistaken for LaTeX commands. Furthermore, you have to replace some characters in your XML files (quite similar to HTML entities). The application that we are planning to release will do this for you, so we'll just give a small overview of what has to be done in the background: • Some specialchars like "$", "{", "}", "_", etc. have to be quoted by adding a preceding
Transforming XML to PDF with LaTeX backslash • "..." should be replaced by "\dots" • \\ is used to mark the end of a paragraph, remember to include it in your templates. • ~ is used to explicitly create a space, similar to in HTML A list of all pitfalls would be nearly endless, given the possible level of complexity of LaTeX and XML documents. If you experience problems with any special characters you should take a look the LaTeX documentation to see if the characters you used have any relevance in LaTeX documents.
About The Author
?>
Stephan Schmidt is a web-application developer from Karlsruhe in Germany. He started coding PHP about three years ago and decided to join the Open Source community in 2001. He is a founding member of PHP Application Tools (http://www.php-tools.net) and author of patTemplate, patXMLRenderer, patUser and other classes.
Click HERE To Discuss This Article http://www.phparch.com/discuss/viewforum.php?f=37
Useful Links LaTeX Project: http://www.latex-project.org/ patXMLRenderer: http://www.php-tools.de/site.php?file?patXMLRenderer/overview.xml patTemplate: http://www.php-tools.de/site.php?file?patTemplate/overview.xml teTeX: http://www.tug.org/tetex/ MikTeX: http://www.miktex.org
Nobody...
As the publishers of Ian's Loaded Snapshot we know OSCommerce!
Hosts OSCommerce Better!
100's of OSCommerce powered sites rely on our years of experience with OSCommerce, direct
We Guarantee It! PHP, mySQL and Curl Optimized for OSCommerce Free Shared Certificate & Integrated SSL Server 20+ Contributions Pre-Installed on MS1 Release Web Mail and Web Based File Manager Full FTP and phpMyAdmin Access Free Ongoing Hands-On Support Web Stats by Urchin Reports Free Installation and Configuration
USE PROMO CODE: phpa Get an Extended Free Trial and Free Setup! August 2003 · PHP Architect · www.phparch.com
866-994-7377 or
[email protected] www.chainreactionweb.com www.chainreactionweb.com/reseller.
15
Ming & PHP
F E A T U R E
By Seth Wilson
T
he other day, while I was perusing through some books at a local bookstore (computer books of course), I noticed a book in the computer section about Ming. "Ming", I said to myself, "never heard of that". So I began to leaf through the book and discovered that Ming is a library of code with a set of wrappers that one can use to dynamically create .SWF format files (compiled Flash Movies) using PHP. "Oh, this is too cool", I can remember saying; however, since I had already blown my book budget on some others, the Ming book went back on the shelf. I did promise myself, however, to look into this Ming library a little bit more once I returned home. So I turned to the Internet (a fantastic invention, this Internet thing), and found plenty of documentation and examples to get my cranial juices flowing. I've finished my journey into cyberspace to seek out Ming, and I'd like to share with you my findings. Don't worry there won't be a three hour slideshow. Let's take a little journey into the world of Ming with PHP The first stop is to investigate the website for the Ming library, and this site is located at http://ming.sourceforge.net. Take the time to visit the site and scour through the function reference. There is a nice list here, and using these classes and their methods we can do just about everything you can do within the Flash authoring environment. You can even add Actionscript, which is the built-in scripting language inside Flash. I think this library is great, and a lot of nice work has
August 2003 · PHP Architect · www.phparch.com
been done here. My one beef is they could have named the objects better to represent the Flash "lingo". For instance, in Flash you would refer to an animated object as a "movie clip", but the Ming project leaders decided to call the class SWFSprite. A small thing, but nonetheless for a PHP developer beginning to learn Flash and Actionscript, this could be confusing when trying to get references and examples from the Flash community. Also check out the mailing lists archive located at http://www.opaque.net/pipermail/ming-fun/. If you are having a problem, chances are good that someone else has had the same problem, and there may be a work around or a solution there. Did you ever have to make up your mind? There are some cases where it makes sense to use the Flash authoring environment, and there are some cases where it would be nice to dynamically build a Flash movie. As we work through some examples you will begin to see that if you have an elaborate idea it may be best to draw it in Flash, especially if your idea contains plenty of animations. Even if you were to get REQUIREMENTS PHP Version: 4.1.1+ O/S: Any Additional Software: A browser with Flash Player installed, Ming 0.2a library, MySQL Code Directory: ming
16
FEATURES fancy and really optimize the PHP code, coding a Flash movie is a lot of work. Even still, I can think of a few applications where a dynamically generated .SWF file would be far easier to code than to draw. A couple of cool applications I can think of would work great for a website that offers advertising space. For a basic advertising package you could create a PHP web application where companies wishing to create a web banner can pick from a set of pre-coded Flash movies, and they include their own text and graphics. That way they can advertise without going through a lengthy design and development phase just for a simple web banner. Heck, they don't even have to know Flash. You could also have a dynamically created photo gallery or slideshow based on a user's query, and add some neat fade in/fade out or other effects. Or you could create custom e-cards where the content is delivered all in Flash. Actually, that's a great idea. Nobody else use that idea, ok? Make sure you check out a method in the SWFMovie class called streamMP3(). This one caught my attention immediately. I figured what's a better example than to make a Ming MP3 player, but first we need to learn some fundamentals. Ming compiles your PHP code into Flash version 4 movies, and at the same time any Actionscript you embed is compiled to Flash version 5. Why that is I don't know, so if you are looking to add Actionscript, please remember it must conform to Flash 5 Actionscript syntax, which is slightly different than the newest version, Flash MX. The very latest CVS Ming version 0.3a has support for Flash MX. However, this version is not released yet, so this article will focus on Ming 0.2a. If you do not presently have a browser with Flash player 6 you will need to download and install it. Flash player 6 is avail- Figure 1 able from Macromedia's website (http://www.macromedia.com) and is backwards compatible, so even though Ming creates Flash 4 movies, they will still be viewable with Flash player 6.
Ming and PHP should be a line just below this that begins with "extension_dir =". We want to change this line to reflect the location of our PHP extension directory where a file called php_ming.dll resides. If you have the default install of PHP (c:\php) on a Windows box it will probably be extension_dir = c:\php\extensions\
The second modification to the php.ini file is to uncomment the line that reads "extension=php_ming.dll", under the section labeled "Dynamic Extensions". Installing Ming on a Linux system is a whole different story. There are pre-compiled PHP modules on the Ming website, but these are compiled for older versions of PHP. That means you must build Ming support into PHP yourself, unless you can find a nice soul who has done the compiling for you. There is some documentation on how to do this located at http://ming.sourceforge.net. After installation, restart your Apache server and badda bing - you are ready to roll. Of course, if you do not want to cause yourself grief, make sure that Ming is working properly by writing a little script as in Listing 1 and running it. If Ming is installed properly you should be able to scroll down and see something similar to Figure 1. If you still have problems check out your Apache log file, it sometimes sheds some light on things. Listing 1 1
Installation The installation on a Windows machine is fairly straightforward as long as you are using a recent build for Windows. I am running PHP 4.3.1 on a Windows 2000 machine, and the Ming extension has already been compiled into PHP. All we need to do is enable Ming by modifying the php.ini file. Locate and open the php.ini file on your system and scroll down until you find a section labeled "Paths and Directories". There August 2003 · PHP Architect · www.phparch.com
17
FEATURES Flash conventions Before we get into using Ming and draw up some movies, we should go over some conventions with Flash. Flash movies have the origin of the movie at 0,0. This point is in the uppermost left corner. From this point the x value increases from the left to the right, and the y increases as we go from top of the screen downward. The other hot tip to know is that an object's depth in the movie corresponds to the order you added it to your movie. Have a look at Figure 2 to see what I mean. SWFMovie The first object we should look at in the Ming library is the SWFMovie object. This is the object that handles the creation of our movie, setting the frame rate, the overall dimensions, the background colour, and also handles the addition and removal of "items". These items can be shapes, other movies, movie clips (or sprites), buttons or text. If you want to see it (or hear it), you must add it. Listing 2 shows the setup code necessary for our movie. Don't bother viewing the movie at this point because all you will see is a blank white screen. As you can see from Listing 2, I like to add the code to instantiate the movie, and set all the settings right at the beginning. Then I like to jump down to the bottom and include two more lines to spit out the Flash movie to the browser for viewing. Listing 2 1 2 3 4 5 6 7 8 9 10 11 12 13
setDimension(320, 240); $m->setBackground(255, 255, 255); // All the rest goes in here! header('Content-type: application/x-shockwave-flash'); $m->output(); ?>
Figure 2
Ming and PHP
header('Content-type: application/x-shockwave-flash'); $m->output();
The first line will tell the browser what type of file this is, and the next line outputs the finished .SWF file to the browser. Alternatively, you can output the Flash movie to a SWF file by using $m->save($filename);
SWF_Shape Shapes are drawn using the SWFShape object, and shapes are the foundation to a Flash movie. You can take a shape that is drawn and use it for background fills, add it to a movie clip or sprite, or create a button with it. At the same time we can fill this shape with a colour, gradient or bitmap, as well. Enough talking already! Let's draw our first shape (see Listing 3). In this first example we are using a few methods of the shape object. First off, we create a new SWFShape Listing 3 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
setDimension(320, 240); $m->setRate(12.0);
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 ?>
August 2003 · PHP Architect · www.phparch.com
//Starting X and Y values $x = 100; $y = 100; //Height of trangle $height = 50; // create a new Shape $s = new SWFShape(); //Set the line width and colour $s->setLine(2, 0, 200, 0); // create a new Fill red, green, blue and alpha $f=$s->addFill(0,255,0,255); // set the fill to the new fill just created $s->setRightFill($f); // Start drawing the Shape $s->movePenTo($x, $y); $s->drawLineTo($x-(0.5*$height),$y+$height); $s->drawLineTo($x+(0.5*$height),$y+$height); $s->drawLineTo($x, $y); // add it to the movie $m->add($s); $m->nextFrame(); // and output header('Content-type: application/x-shockwave-flash'); $m->output();
18
FEATURES object, which in this case we are calling $s. Then we use the setLine() method to set the line thickness (the first argument) and the colour (the last three arguments - red, green and blue). In this case, we specify a dark green. The line $f=$s->addFill(0,255,0,255);
adds a light green fill (the first three arguments) to the outline we are about to draw. The last argument to addFill() is the "alpha" of the fill. Alpha can be best explained as the opaqueness of the fill, with 0 being transparent and 255 being fully opaque. The setRightFill() and setLeftFill() methods are best explained in the Ming documentation. Now that we've told Ming what we want to draw, we can now give the commands to draw. The best way to visualize this is to think about actually drawing on paper by hand. First you move your pen to a spot, put it down on the paper then draw a line to another point. This is the exact same process used by the movePenTo() and drawLineTo() methods.
Ming and PHP One note that I should mention here is when you are coding any Ming project, step by step is the best plan of attack. Make sure that you have display_errors on and, as a rule of thumb, don't put your head down and bang out three hours of code, because you'll spend the same amount of time, or more, debugging. If you run Listing 3 in your browser, you should see something similar to Figure 3. SWFGradient Instead of a straight colour fill, you can also fill a shape with a gradient using the SWFGradient object. A gradient is basically a smooth transition from one colour to another. There are two types of gradients: a linear gradient, which is a line; and a radial gradient, which is circular. Listing 4 shows a basic gradient fill. The code is setup to show the linear gradient first. Just comment out the first line calling addFill() and uncomment the line below it to see the radial gradient. Make sure you run the script in your browser to have a look at what it does.
Figure 3
August 2003 · PHP Architect · www.phparch.com
19
FEATURES
Ming and PHP
SWFButton Buttons in Flash act as a user interface with the movie. You create a button with Ming by using the SWFButton object. A button is basically a specialized movie clip. Once you create the object, you can add previously-drawn shapes to define the shape and size of the button. Without a shape, a button will not be seen by your users. A basic button has up, down, over, and hit states, and you can define different shapes for each button state to create some neat effects. One downfall with Ming is you cannot add text or other shapes to your button. You can work around this by first drawing the button, then drawing another shape over top. We will use this technique in our MP3 player. Have a look at Listing 5 (included in this month’s package), and then run this script in your browser to see the button in action. You can tell a shape is a but-
ton, because when you mouse over the button the mouse icon will change from an arrow to a hyperlink icon. The future looks painful. Let's create a class. drawShapes class I hope you're starting to see that if you have a movie with a bunch of buttons and shapes, it will become very painful to code. The last example has over fifty lines just to draw the shapes for one button. If you had even five buttons in your movie, your code would look very messy. We could create some functions that will do some of the drawing, but this is still cumbersome because you either have a function with a huge number of arguments, or it would really only save a couple of lines of code. There is a solution to this problem: another object, of course. I developed this class (shown in listing 6 - also included in this month’s package) to aid in the drawing of shapes. Feel free to add more
“... don't put your head down and bang out three hours of code, because you'll spend the same amount of time, or more, debugging."
Listing 4 1 setDimension(320, 240); 5 $m->setRate(12.0); 6 7 8 // create a new Shape 9 $s = new SWFShape(); 10 11 //Set the line width and colour 12 $s->setLine(2, 0, 200, 0); 13 14 $g = new SWFGradient(); 15 $g->addEntry(0.0, 0, 255, 0, 255); 16 $g->addEntry(1.0, 255, 255, 255, 0); 17 18 $f = $s->addFill($g, SWFFILL_LINEAR_GRADIENT ); // Comment this to see radial 19 //$f = $s->addFill($g, SWFFILL_RADIAL_GRADIENT); // Uncomment this to see radial 20 $f->scaleTo(0.25); 21 $f->moveTo(100, 100); 22 23 24 // set the fill to the new fill just created 25 $s->setRightFill($f); 26 27 // Start drawing the Shape that defines the fill 28 $s->movePenTo(0, 0); 29 30 $s->drawLine(320, 0); 31 $s->drawLine(0, 240); 32 $s->drawLine(-320, 0); 33 $s->drawLine(0, -240); 34 35 // add it to the movie 36 $m->add($s); 37 $m->nextFrame(); 38 39 // and output 40 header('Content-type: application/x -shockwave-flash'); 41 $m->output(); 42 ?>
August 2003 · PHP Architect · www.phparch.com
20
FEATURES shapes as you like. Have a look at Listing 7, which shows the previous example, this time using the drawShapes object. That looks a lot nicer, and is easier to follow. I think so, anyway. Animate…good times come on!!! (Or is that celebrate?) Everyone loves (or hates) Flash movies because of the animations. With the Flash authoring tools, you have a nice interface in which to draw your beginning and end shapes in different frames on the timeline, after which Flash will happily perform all the drawing in between. Not the case with Ming. Now you are the person that will happily draw all the animation in between. Really, it's not that bad. If we use our wits, and some control structures, we can minimize some of the work. We can use the following methods to animate our Listing 7
Ming and PHP shapes. • move(x,y) and moveTo(x,y) • scale(x,y) and scaleTo(x,y) • rotate(degrees) and rotateTo(degrees) • skewX(x), skewY(y), skewXTo(x), skewYTo(y) • and you can also transform colour After looking at Listing 8 (included in this month’s package), which shows some of the methods used to animate our triangle, run the script to see the little guy in action. Also note that you must advance the movieclip or movie frame-by-frame manually using the nextFrame() method. You can also fully animate shapes using only Actionscript, but that's beyond the scope of this article.
1 setDimension(320, 240); 5 $m->setRate(12.0); 6 7 //Starting X and Y values 8 $x = 40; 9 $y = 20; 10 11 //Height of trangle 12 $height = 50; 13 14 include("drawShapes.php"); 15 16 $test = new drawShapes(); 17 18 $test->setShapeLineStyle (2, 0, 200, 0); 19 $test->setShapeFillStyle (0,255,0,255); 20 21 // make the first shape green 22 $b = new SWFButton(); 23 $b->setUp($test->getTriangleShape($x,$y,"up",$height)); 24 25 // make it blue 26 $test->setShapeFillStyle (0,0,255,255); 27 $b->setOver($test->getTriangleShape($x,$y,"up",$height)); 28 29 // make it blue --- a little alpha 30 $test->setShapeFillStyle (0,0,255,150); 31 $b->setDown($test->getTriangleShape($x,$y,"up",$height)); 32 33 // make it blue --- a little alpha 34 $test->setShapeFillStyle (0,255,0,255); 35 $b->setHit($test->getTriangleShape ($x,$y,"up",$height)); 36 37 38 //add the button to the movie 39 $i = $m->add($b); 40 $i->moveTo($x,$y); 41 42 43 $m->nextFrame(); 44 45 // and output 46 header('Content-type: application/x -shockwave-flash'); 47 $m->output(); 48 ?>
August 2003 · PHP Architect · www.phparch.com
Let's build this MP3 player I think we have most of the tools now to build a fully functional MP3 player, and any other tools we will pick up on the fly. I have told a little white lie, it's not really an MP3 player, but rather the player plays a SWF file with an embedded MP3. Either way, the users listening will not be able to download the track. There are two parts to this player. The first part will be a PHP script that will allow a user to upload an MP3 file. The second part is the user interface that will have the play, stop and pause buttons, volume control, and selectable playlist. Upload and add script MP3 files will be uploaded and converted to a SWF file using a separate PHP script from the player. The script, called upload.php, uses a library developed by someone else (no sense re-inventing the wheel) that will read the MP3 header information and the ID3 tag. The header information is needed to grab the length of the song. It is absolutely imperative to retrieve this information because when we embed the MP3 into a SWF file we need to make sure the
21
FEATURES
Ming and PHP
SWF file has enough frames to encapsulate the entire song. The ID3 tag information is stored into a MySQL database and is used by the player to display track information. The HTML form is called addplaylist.html, and is shown in Listing 9. It is the front end for the upload.php script, shown in Listing 10 (included in this month’s package). We need to modify the php.ini file again to make sure the file is uploaded and processed properly. Open up your php.ini file and search for the line that reads upload_max_filesize = 2M
and change to something appropriate like upload_max_filesize = 8M
We also need to change the input and execution times to accommodate the larger sized files. Change these settings to max_execution_time = 90 max_input_time = 90
You may need to fiddle with these settings if you are uploading files larger than 8 megs. As I mentioned, the script that handles the processing is called upload.php and can be seen in Listing 10. The script performs some basic validation of the uploaded file, strips out the ID3 tag and stores that as a new database record. It then processes the MP3 by embedding it into an SWF file. The Ming library is used for the last step, and the newly created SWF is saved to
the local disk. Make sure you have permissions set for the directory to be able to write files, or you could change the script slightly to suit your tastes. There are a few variables in this script that you may have to change to suit your system. The first variable is $uploaddir. You will have to change this path to reflect where your player script resides. The other variables that could be changed are $user and $pwd which are used for accessing the MySQL database. I have run into some problems uploading MP3 files that have no ID3v1 information. I know this is a pain but try and make sure there is ID3v1 information in the MP3's you upload. For those worried about security (and we all should be), having any Regular Joe upload a MP3 file to their server would keep them up at night. Think of this script as an administration tool then, don't put it online, and sleep well. The player interface (See the complete listing for the player in Listing 13, in this month's package) Let's make the interface look cool. I thought that using a radial gradient that transitions from black to white transparent, and placing the center of this gradient in the four corners of the rectangle shape would look really neat. The only way to do this is to create four identically sized shapes and place one gradient in different corners. Run Listing 11 (also included in this months package) in your browser to see the effect I've been talking about. Next up is to add some text fields. We will use three text fields for our player. One text field will contain information on which track is currently being played, another will be a status bar and the last will serve the
Listing 9 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
Add Song To Ming Mp3 Playlist <meta name="generator" content="editplus"> <meta name="author" content=""> <meta name="keywords" content=" "> <meta name="description" content="">
Add Song To Ming Mp3 Playlist
August 2003 · PHP Architect · www.phparch.com
22
FEATURES
Ming and PHP
purpose of a scrolling playlist. I have not covered how to add a text field, so I will do it now. A text field cannot be animated but it can be dynamically changed during the movie's timeframe. When you instantiate the SWFTextfield object, $t = new SWFTextField([$flags]);
you can specify the behaviour of the text field by specifying different flag arguments. The flags are listed below from the Ming documentation. You can set multiple flags by using the bitwise OR operator ( | ). Valid text field flags:
SWFTEXTFIELD_NOEDIT
indicates that the field shouldn't be user-editable
SWFTEXTFIELD_PASSWORD
obscures the data entry
SWFTEXTFIELD_DRAWBOX
draws the outline of the text field
SWFTEXTFIELD_MULTILINE
allows multiple lines
SWFTEXTFIELD_WORDWRAP
allows text to wrap
SWFTEXTFIELD_NOSELECT
makes the field non-selectable
You can also specify the height, width, type and size of the font using the methods of the SWFTextField object. You can even specify the instance name of the text field so that you may populate it dynamically using Actionscript. The status bar text field for our player will be named "status", and will be the default, non-editable text field. The second text field, which will contain track information, will be named "trackinfo". The scrolling playlist will be non-editable, non-selectable, multiline, and will contain HTML code. This is an undocumented flag for
an SWFTextField, called SWFTEXTFIELD_HTML, that will make our text field render HTML code. We want to render this text field as HTML so that each track will be listed with a hyperlink which when clicked on will load that song into the player. Once you see you see the Actionscript it will make more sense. Now we can add our buttons to play, pause, and stop, as well as volume controls and scrolling buttons. The buttons for track control will be circles, and the volume and scroll buttons will be triangles. The drawShapes class comes in very handy. Have a look at the listing to see the addition of the buttons. Let's make our buttons a cool X-box green, with dark green borders With buttons, come actions Of course, now that you have a button, you want to be able to do something once the user clicks on it. You can add Actionscript to a button, movie clip or the movie itself. All actions are added using the SWFAction object. You can add many Actionscript statements inside one add SWFAction command, just make sure to end each Actionscript statement with a semi-colon. It's very simple to add Actionscript to a button. The object has a method called SWFButton addAction(), and the first argument we pass to this method will be an SWFAction object complete with the Actionscript. The second argument is a flag to tell Flash after which button event this code should be called. $b->addAction(new SWFBUTTON_MOUSEUP);
SWFAction("_root.mp3.play();"),
Have you had your PHP today?
• Subscribe to the PRINT edition • Subscribe to the ELECTRONIC edition
Visit us at http://www.phparch.com and subscribe today. August 2003 · PHP Architect · www.phparch.com
php|architect 23
FEATURES
Ming and PHP
For the volume controls, we have a few lines of Actionscript. When the volume up or volume down buttons are clicked the Actionscript global variable called Vol will be incremented or decremented accordingly, then a new Sound object will be created, and will be passed this new volume setting. The scroll up and scroll down buttons also have some Actionscript code. They access a property of the text field called 'scroll'. If the playlist has more items than what can be seen in the text field, these buttons will allow the user to scroll up or down to see more items. This scrolling action is done by incrementing or decrementing the scroll property of the text field.
“For those worried about security, having any Regular Joe upload a MP3 file to their server would keep them up at night.”
Add some fancy touches with MySQL We are going to also harness the power of MySQL, and use it to hold information about the MP3. Ultimately, we are using an SWF with an embedded MP3 file, so we cannot grab any information about the MP3 unless it is stored somewhere else. We are going to use the same database to populate the scrolling playlist, and also to retrieve information about the current track being played. Listing 12 shows the SQL file to import the table structure into a MySQL database. Let's call the database "mp3stream", and import this information into it. Now, last but not least, we can now add a movie clip
or SWFSprite that will act as a container for the MP3 embedded SWF file. Let's call this movie clip (sprite) "mp3", and leave it empty for now because we are going to add the MP3 dynamically using Actionscript code. Actionscript for the movie There are only a few lines of Actionscript to get this whole movie going (see the bottom of Listing 13). The first thing we do is set the initial volume using the previously mentioned Vol Flash variable. Then we load the SWF movie containing our MP3 using the loadMovie function into the empty movie clip called "mp3". Next, we create a new Sound object to handle the volume initially. Lastly, we add the track information into the textfield. That's it. We are ready to press play….so go on, press play. All of this code is neatly commented and can be seen Listing 12 # # Table structure for table `mp3info` # CREATE TABLE mp3info ( mp3ID int(11) NOT NULL auto_increment, mp3Filename varchar(255) NOT NULL default '', mp3Length int(11) NOT NULL default '0', mp3Title varchar(255) NOT NULL default '', mp3Artist varchar(255) NOT NULL default '', mp3Album varchar(255) NOT NULL default '', mp3Year varchar(10) NOT NULL default '', mp3Comment varchar(255) NOT NULL default '', mp3Genre varchar(255) NOT NULL default '', PRIMARY KEY (mp3ID) ) TYPE=MyISAM;
Tap Internet PHP Training Courses ?> Been handed a PHP project and don't quite know where to start? Itching to learn PHP in a classroom environment? Tap Internet has trained hundreds of PHP developers over the past 2 years, and is the largest independent PHP training organization in the U.S. We provide complete PHP training solutions for all levels, including on-site courses. PHP Beginner- $999
PHP Advanced - $1999
LogiCreate - $1299
• • • •
• • • •
• • • •
Next class: Sept 22-24, 2003 A 3 day course providing beginners a solid foundation in PHP. • Basic programming concepts Dynamic HTML pages Loops/control structures Array handling File system management
Next class: Sept 15-18, 2003 A full 4 days with all topics from PHP Beginner plus: • Session management Objects PDF creation Database usage Security issues
Next class: Sept 29-31, 2003 A 3 day course covering the LogiCreate application server. • Installation User management Custom permission systems Rapid application development Error handling
All classes feature hands-on instruction with friendly experienced teachers. Each student is assigned an individual workstation, and class sizes are limited to increase personal instruction. Lunch and snacks are also provided. Call 1-866-745-3660 or 734-480-9961 to book your seat now or visit http://www.tapinternet.com/php August 2003 · PHP Architect · www.phparch.com
24
FEATURES in Listing 13. The finished player can be seen in Figure 4. How to make this better To make this script a full blown application, we would have to add a few touches to really make it polished. A pre-loader would be essential to show users on slow connections that the MP3 is loading. A Flash version check would also be really slick. That way you can direct users that do not have the Flash plug-in installed to Macromedia's site. The ultimate touch would be a Winamp-style visualizer with some cool animations that would really make your CPU smoke. You could change the architecture of this script a small bit, and have the script embed the MP3 into the SWF on-the-fly (rather than creating it up-front). This would limit the size of media files on your server, but will slow the performance of the script significantly. A second step in the file upload procedure allowing users to add or change the ID3 information before saving to the database would be nice feature, I think. Also adding more shapes to the drawShapes object would be nice. Hearts, stars, more geometric shapes like pentagons, hexagons and the like would be really helpful, as well. So what is this script really good for? I think this script would be great for anyone who wants
Ming and PHP to offer some audio content on their website. If you are a motivational speaker or a local garage band, and you don't want people to grab your content this might be a solution for you. Since this player uses Flash, you are going to reach nine out of ten Internet users without them having to download a separate plug-in. Acknowledgements I would like to thank a regular contributor to the Ming mailing list, Armel Grignon at mingshop.arpane.net, who indirectly, through his posts to previous queries, helped me immensely. I would also like to thank the author of the mp3.inc.php script. I searched high and low for the author, but came up empty. Thanks for saving me some work!
About The Author
?>
Seth Wilson runs his own computer consulting business based in Waterdown, Ontario, Canada. He enjoys breaking and fixing computers, writing code, playing, watching, and talking hockey and rollerblading with his wife Trish
Click HERE To Discuss This Article http://www.phparch.com/discuss/viewforum.php?f=38
Figure 4
August 2003 · PHP Architect · www.phparch.com
25
Granted! Announcing the winners of the 2003 php|architect Grant Program
A
fter months of patient wait, hundreds of submissions and hours of deliberation, it’s finally time to pick two winners for our Grant Program. The choice was made particularly tough by the fact that of the over 200 submissions we received, many were either original applications that broke new ground, or established platforms that were looking to expand in new directions. However, having a clear goal helped us a great deal: we wanted to find (and fund) two projects that were capable of demonstrating that PHP is a worthy platform in highly demanding environments and industries. In the end, we had to make a choice... so, to quote Zapp Brannigan from Futurama, “without further adieu”, here are this year’s winners! Nurse—I need some PHP... stat! There are few industries that are as demanding and exacting as healthcare. After all, a mistake of any kind could mean serious harm to a patient—this is an environment in which the expression “blue screen of death” takes on a whole new (and much more ominous) meaning. We were quite surprised, therefore, when we found out that a group of developers were working on a hospital administration platform based entirely on PHP, called Care 2002. After a chat with the team leader, and a quick visit to their website we (http://www.care2x.com) were convinced that this team was on to something big. A hospital implementing a Care 2002based management solution would be a great vote of confidence for PHP from an industry sector where “tolerance for error” is usually in the same sentence as “zero”. “Care 2002 is software for
hospitals and health care organizations, designed to integrate the different information systems existing in these organizations into one single efficient system,” says team leader Elpidio Latorilla. “It solves the problems commonly found in a network of multiple programs that are incompatible with each other. It integrates almost any of the services, systems, departments, clinics, work processes, data, communication, etc., that exist in a hospital. Its design can even handle non-medical services or functions like security, maintenance, surveillance, etc. It is also user-configurable, modular and scalable.” Care 2002 uses standard SQL. The use of a single data format solves the problem of data redundancy. With its database abstraction layer, it can support different databases. The entire system is web-based, and all its functions can be accessed with a common web browser. Therefore,
there is no need for special UI software on the client side. Since all program modules are processed on the server side, updates and extensions do not require changes on the browsers, eliminating the need for network interruptions and downtimes. According to Elpidio, his team chose to use PHP for their project mainly because of its stability, portability and ease of use. Even though it was started in 2000, Care 2002 is still in beta—for the most part, due to the grueling requirements imposed on it by its intended audience. Still, many parties have expressed interest in implementing it at their healthcare facilities once it exits from its long beta testing cycle. We’re happy to announce that our grant will be put to good use. As the project’s final stable release comes close, the team is planning to do numerous presentations, and our contribution will go towards the purchase of hardware needed for these events. Assisting the success of a project like Care 2002 is what makes the Grant Program so important to us.
work management tools written entirely in PHP. According to team leader Javier Szyszlican, it started as an experiment in 2002 and it has rapidly become a widely-used open-source project, fueled by the power and ease of deployment of PHP. JFFNM, which is used in about 300 mid-sized networks, contains a total of over 16,000 lines of code. That may sound like a lot, but given its functionality, we were surprised that so little was needed to make it function. JFFNM supports all sorts of network management functions, such as SNMP integration, Tacacs+ Authentication and Accounting, Syslog Logging with PCRE Matching, SNMP Trap Handler, TFTP Configuration, Smokeping, MSyslog and Syslog-NG. “Using PHP allowed us to reuse all possible code,” says Javier. The JFFNM team has a long list of needs that our grant will help to cover, from new hardware to actual development time for the introduction of new features, such as network auto-discovery. Congratulations to the entire team, and keep up the good work!
Let’s manage a network... just for fun The second winning entry in our program comes from a group who’s trying to work in what is perhaps one of the more demanding sectors of I.T.: network management. Just for Fun Network Management (JFFNM), available at http://jffnms.sf.net/, is a complete suite of extensible net-
php|a
More screenshots at http://jffnms.sourceforge.net/shots.php.
The designers of PHP offer you the full spectrum of PHP solutions
Serve More. With Less. Zend Performance Suite Reliable Performance Management for PHP
Visit www.zend.com for evaluation version and ROI calculator
Technologies Ltd.
Datanamic DeZign for Databases 3
R E V I E W
By Peter James
Quick Facts Price: Single User License $229.00 5 User License $916.00 10 User License $1603.00 Free Trial: 30 day unrestricted free trial System Requirements "DeZign for Databases" is available as a 32-bit application. The product is available for Windows 95 or later, Windows NT, Windows 2000 and Windows XP. "DeZign for Databases" requires a minimum of 16MB RAM and will run on a 486 processor. Disk space requirements is 6 MB. Supported Databases
Oracle Interbase IBM DB2 MySQL dBase Paradox MS SQL Server MS Access SQLAnywhere
Sybase Informix Pervasive Advantage DB DBISAM FoxPro CA Clipper PostgreSQL
Homepage: http://www.datanamic.com
August 2003 · PHP Architect · www.phparch.com
F
or months now I’ve been waiting for Dezign 3. I’ve faithfully used Dezign 2 to design almost every database I’ve created in the last two years. Its combination of support for multiple database servers, feature list, and price were unbeatable. Unfortunately, though, I’ve been recently searching for a replacement. Dezign 2 was two years old, with no major updates, and was beginning to show its age. Much to my dismay, there are no products even close to its price point. Finally, earlier this month, Dezign 3 was released. It features a shiny new interface and lots of new features. I was eager to play with this new version, and I thought I would share my experience with you. What is Dezign? In their own words, Datanamic Dezign “... is a database development tool using an entity relationship diagram. It visually supports the lay out of the entities and relationships and automatically generates SQL schemas for most leading databases.” I couldn’t have said it better. Dezign allows you to visually design your database schemas. If you’ve ever worked with a large CMS like Drupal or PostNuke, you might be vaguely familiar with the enormous database structure lurking beneath. Imagine trying to design that structure in phpMyAdmin. Simply specifying the final layout of a system like this could be a traumatizing experience. If you do any medium- or large-scale work with databases, you know that having a visual representation is imperative, especially in the design phase. Using a tool like Dezign, I can try out my wacky database layout
29
REVIEWS ideas with little effort. I couldn’t do that easily on the command-line, or using a tool like phpMyAdmin. Plus, being a visual person, I enjoy the abstract representation that modeling tools like Dezign offer. Without further delay, let’s take a look and see if this new version of Dezign stacks up. Installation and Configuration I downloaded the 30-day trial from the Datanamic website, and ran the installer with no problems. Although there are plenty of ways to configure the interface, no initial configuration was necessary. On first run, a sample entity-relationship diagram (ERD) came up automatically. Dezign ships with a number of example files, which could be very helpful to someone just starting out. One thing to note for anybody upgrading from Dezign 2 to Dezign 3 is that it will automatically convert your v2 files to v3, letting you know what it had
Datanamic DeZign for Databases 3 problems with. Note that this conversion is irreversible, although Dezign helpfully (and thankfully) saves a backup copy of your v2 file. Designing with Dezign There’s certainly a lot of functionality in Dezign 3. As you start to build your ERD, you are afforded a great deal of control over your design. As an example of the plethora of options, let’s look at adding a table (or entity). Upon specifying that you want a new table, you have the option to add attributes (or fields), indexes, triggers, table constraints, extra scripts, a detailed description, and a set of TODO’s. And each of these options has it’s own set of detailed options. Amazingly, the huge amount of configurability does not detract from the simple tasks. If you just want to get your structure in there, it’s very fast. If you want to take your time and set everything up with proper
Figure 1
August 2003 · PHP Architect · www.phparch.com
30
REVIEWS defaults, constraints, and indexes, then it’s going to take a little more time. A really cool feature is that you can maintain multiple diagrams (or views) of your main schema. This includes subsets of tables, meaning that you can have a main diagram of your whole ERD, and then provide more concise and more documented diagrams of the user management system, or the commerce system, all the while maintaining only one physical table set. Adding relationships between entities has become really easy in Dezign 3. One-to-many relationships are doable in two clicks and a drag. Many-to-many relationships require a comparable amount of work, but automatically create the association mapping table, which saves a fair bit of effort. The help system in Dezign is quite thorough, and even includes a section on database theory. I must mention, though, that the Dezign help system doesn’t contain a single screenshot or icon. This may sound nit-picky, but – again – I’m a visual person. Explaining the options on a dialog or a toolbar is fine, but I like pictures. Especially ones with arrows. Trying to support as many database systems as Dezign does is an amazing feat, especially if its done well. I can’t comment on the other RDBM systems sup-
Datanamic DeZign for Databases 3 ported by Dezign, but PostgreSQL support is actually quite good. There are a couple of quirks here and there, but nothing that will prevent you from being miles ahead of where you were. And some of these quirks can be immediately addressed by one of Dezign’s coolest features: the template system. Dezign allows you add your own new database type. I found this interesting, and poked around trying to find out how it works. Dezign maintains a list of templates for each database type that it supports. There are a handful of files necessary to add a new type, but usually it is acceptable to just copy and modify files from other database types. Besides the configuration directives and data type specifications, you also must provide a template file. The templating language is a subset of Pascal, making it pretty easy to work with. Although there is zero documentation (that I could find) on the templates, I found I could make significant changes inside of about 20 minutes of first looking at the files. Of course, you probably want to back up your original copies first, so you don’t irreversibly change something important. While the support for triggers and procedures is there, it’s pretty basic. There is a semi-highlighting code editor, but it doesn’t do much more than that. In
Figure 2
August 2003 · PHP Architect · www.phparch.com
31
REVIEWS
Datanamic DeZign for Databases 3
order to add triggers to PostgreSQL using Dezign, I’d have to pretty much not use the trigger interface, and just stick with the general SQL editing box. But I’m going to say that this is really just a function of having to support multiple database systems. A very simple report generator has been incorporated into Dezign 3. This allows you to specify complex templates (again, using the Pascal subset), and build your own reports. The HTML reports require a stylesheet, and this allows even more flexibility in your reports. Dezign offers the ability to print your ERD, as well as exporting it to any of a number of image types. This is nice for presenting your design to pointy-haired bosstypes. Dezign helps you manage document versioning. The system is pretty simple when compared to CVS, or even RCS, but it is nice to have it built in. Speaking of CVS and RCS, Dezign’s file formats are all plain text, including some XML. This means that standard version control systems will work properly and usefully. What I liked I really like the easy specification of table relationships. That’s going to save me lots of time. The template paradigm is awesome. Datanamic has really taken a step forward and put the power back where it belongs. If you can offer this sort of extension system to your users, your product will almost certainly enjoy a longer and happier life. The diagram system is great. Being able to cut up your larger ERD into small bite-sized pieces is invaluable when it comes to explaining it to other people.
What I didn’t like I didn’t like the help system; I found it was plain. A help system may not seem like an important bell to ring. I mean isn’t the help content the important part? Yes and no. The help system is what acquaints users with your product. If your help system is not strong and engaging, people are not going to learn your product properly. I didn’t like the lack of documentation on the templating systems. Perhaps this is in the works, but it is going to be noticed and people are going to want to use it. We need documentation. The support for triggers and procedures leaves much to be desired, at least for me. But again, this comes down to supporting many database types.
In conclusion After using Dezign 2 for two years, and really enjoying it (for the most part), I’m really liking Dezign 3. It has a very nice interface, lots of features, and an eye for the future. I suggest you download the evaluation copy, and see if it can make you more productive. I give Datanamic Dezign for Databases a 4 out of 5.
php|a
Connect with your database Publish your data fast with PHPLens PHPLens is the fastest rapid application tool you can find for publishing your databases and creating sophisticated web applications. Here’s what a satisfied customer, Ajit Dixit of Shreya Life Sciences Private Ltd has to say: I have written more than 650 programs and have almost covered 70% of MIS, Collaboration, Project Management, Workflow based system just in two months. This was only possible due to PHPLens. You can develop high quality programs at the speed of thinking with PHPLens
Visit phplens.com for more details. Free download.
August 2003 · PHP Architect · www.phparch.com
32
Sockets: Part 2
F E A T U R E
By Eugene Otto
In one of last month's articles, we talked about building a client/server system with PHP and xinetd. This month, we'll run through a couple of case-studies showcasing a couple of ways to employ this very powerful technique. We'll start with a quick review.
Review There are two main parts to our system, the client and the server, both written in PHP. The client part, when executed, connects to a server through a port and address using the fsockopen() function. In the examples we cover in this article, the client will actually be used to connect to a number of servers. The server is executed when it is called by the xinetd daemon. xinetd is the successor to inetd, and is common to recent distributions of RedHat. Xinetd is a super-server - a server that listens on many ports and executes the appropriate service when a request is made to a certain port. For example, if xinetd were watching port 23, it would probably start the Telnet service when a request came through. In this article, we'll examine two scenarios. The first deals with a small web hosting company with three different servers having trouble centralizing control of their accounts. The second scenario demonstrates the construction of a simple load monitoring system for several servers. Let's get started! Case study 1: Multiple-Server Control Panel Let's imagine that we're running a small web-hosting company and that we're operating a couple of web servers. Our web servers each came packaged with a web-based control panel, but it's clunky and slow and August 2003 · PHP Architect · www.phparch.com
we want a better way to manage customer accounts. Fortunately, the web servers also came with a fairly robust suite of command line account-management utilities. "But," a small voice in the back of your head may ask, "how can we turn these sleek command line utilities to our advantage?" "With our PHP client/server solution of course!" the copious other voices inhabiting your mind should instantly reply. ;-) Let's think this through. We have several web servers to operate on, each one with a set of utilities we can use to manipulate customer accounts and we'd like to be able to manage all of the web servers from a single control panel instead of with each individual machine's control panel - how can we put this together? We'll start with the idea of a central control panel. The obvious candidate for this is the PHP client. What about the PHP server? Not-so-coincidentally, our web servers are the ideal choices for the server part of our system. REQUIREMENTS PHP Version: php 4.0.6+, CLI, O/S: Unix/Linux Additional Software: MySQL Code Directory: sockets2
33
FEATURES Figure 1 shows a basic diagram of the setup of our client-server system. Figure 1
The Client The client will have access to customer data (through a MySQL database, we'll say). A few fields would be domain name, service plan, e-mail address, username, password, and a unique ID number to help us identify accounts. Listing 1 shows the SQL code for creating the table we'll be using. Listing 2 shows a row of our database where I've inserted some data for testing. Listing 1 CREATE TABLE customers ( Domain varchar(255) NOT NULL default '', ServicePlan varchar(32) NOT NULL default '', Email varchar(255) NOT NULL default '', Username varchar(32) NOT NULL default '', Password varchar(32) NOT NULL default '', id int(5) NOT NULL auto_increment, PRIMARY KEY (id), KEY id (id) ) TYPE=MyISAM;
Sockets: Part 2 debugging sessions, I've often opened up my HTML source to try to find a problem but been dismayed to find one long line of HTML. Using our PrintLn() function instead of print or echo will space out our HTML output very nicely and will help us keep our PHP code free of new-line characters. PrintSoc() is similar to PrintLn() except that it uses the fputs() function to print to a network socket. Although it doesn't impact HTML output, it does add a necessary new-line character which is recognized by servers as a separator argument. It also cleans up our PHP code by minimizing the number of new-line characters we have to append in the actual code. Next, the if-statement checks to see if our form was submitted. If so, a connection is opened to the server that was chosen. The server we'll be writing later on will run on port 64401, so we're setting up our client to connect to that port. If the connection fails, an error is reported. Otherwise, our pass-phrase and the customer's domain name, service plan, e-mail address, username, and password are sent across the server connection. The pass-phrase is a form of authentication known as a handshake where the client and server basically introduce themselves. Finally, we read and print out any output that is sent from the server until the connection is closed - hopefully the information the server sends us will say that the customer's account was created successfully. Figure 2 shows a screenshot of what our barebones control panel looks like in a browser. Although we've left it out here, last month's article discusses encryption as an important security measure. Figure 2
The client will also contain a list of our web servers and their addresses. In this simplified control panel, the administrator will be able to view a customer's records and select a server for the customer's account. Our client code is shown in Listing 3 (included in this month’s package). We start out by connecting to MySQL and storing a customer's data in an associative array - note that for simplicity, I've hard-coded the customer's ID number into the script instead of first printing a list of customers for the administrator to choose from. Next, I've thrown in two functions that I often use to help me Listing 2 organize my PHP code, as well as mysql> select * from customers; +------------- +------------- +------------------ +---------- +---------- +----+ the HTML output. PrintLn() | Domain | ServicePlan | Email | Username | Password | id | simply echoes whatever was sent +------------- +------------- +------------------ +---------- +---------- +----+ | phparch.com | Corporate |
[email protected] | testuser | testpw | 1 | to it in $Line and appends a +------------- +------------- +------------------ +---------- +---------- +----+ new-line character. During 1 row in set (0.00 sec) August 2003 · PHP Architect · www.phparch.com
34
FEATURES Encrypting the customer's information before transmitting it to your server would be a wise decision in order to try to be as safe as possible. That about does it for our PHP client, now let's work on our server. PHP Server The purpose of our server (shown in Listing 4) will be to act as an intermediary between our client and the account creation utilities local to the server. It will need to be executed from the command line, so we'll have to add a few arguments usually not seen in a normal PHP script. If you're unfamiliar with shell scripting, the most apparent difference is probably line 1. This line simply tells the operating system the location of our parser. The -q argument tells the PHP parser to go into quite mode, which means that the HTML headers that are usually added to our output will be suppressed. The next two statements set STDIN and STDOUT as streams to standard input and standard output. These constants will be used as the means for reading and writing information to and from the client. Note that if you're using PHP 4.3.0 or above, the -q argument on line 1 and the definitions for STDIN and STDOUT are unnecessary as they will be defined by default. Next, we read in our pass-phrase from STDIN using the fputs() function and trim off any white space that might have come through. We compare the passphrase to our hard-coded answer, and if it doesn't match, the script is exited. If all is well thus far, we continue on and read in the customer's account information. I haven't included it here, but we can put in some error checking and return Listing 4 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
#!/usr/bin/php -q
$Command = "./CreateAccount -d $Domain -p $ServicePlan -e $Email -u $Username -p $Password"; 17 $Output = shell_exec ($Command); 18 19 fputs (STDOUT, $Output); 20 21 fclose (STDIN); 22 fclose (STDOUT); 23 exit (); 24 ?>
August 2003 · PHP Architect · www.phparch.com
Sockets: Part 2 an error message if a variable turns out to be invalid. We now construct a command string with the path to our account-creation utility and the necessary arguments. I've chosen to use the shell_exec() function to execute the utility so that the utility's output will be returned, giving us a little more control over how to store it. In this example we've simply returned it to the client by printing it to STDOUT, but we could have stored it in a local database or used any of a number of other methods to communicate the result of the command. We finish by closing the STDIN and STDOUT streams and exiting the script. One last thing we have to do before this script will run is chmod it so that our system knows that it's an executable file. See how this is done below. Notice the changes in permission settings. [root@ns1 root]# ls -l server.php -rw-r--r-- 1 root root 5 Jul 8 04:25 server.php [root@ns1 root]# chmod 755 server.php [root@ns1 root]# ls -l server.php -rwxr-xr-x 1 root root 5 Jul 8 04:25 server.php [root@ns1 root]
Before we move on, let's test out our server script on the command line to make sure it works. I've included the code for a dummy account-creation script in Listing 5. To use it, save it as a file named CreateAccount in the same directory as server.php. Follow the same chmod process that we used for server.php. [root@ns1 root]# chmod 755 CreateAccount
Listing 5 #!/usr/bin/php -q
As you can see, the dummy script is very minimal and does absolutely nothing with the arguments sent to it, but it will provide an indicator of how well server.php works. Now, follow the interactions shown in Listing 6. We first execute the server and then enter a few values to make sure that everything works as expected. Listing 6 [root@ns1 root]# ./server.php PHP|ARCH phparch.com Corporate
[email protected] testuser testpw Account created successfully!
35
FEATURES
Sockets: Part 2
If your script doesn't run correctly, here are some tips that might help: • If you try to execute the script but get “No such file or directory” bash error, your path to the PHP interpreter on line 1 of the script is probably incorrect. Run the command whereis php to find its location and experiment with the paths that are returned. If it still doesn't work, then it's possible that PHP is not configured to run as an executable on your system. Try php.net for help on recompiling or reinstalling PHP with command line processing enabled. • If you get a “Permission Denied” bash error, then you might not have chmodded your script correctly. You need to run the command chmod 755 server.php before it will execute properly. Hopefully we've now got a tested and working server. Let's set up Linux to accept remote requests. As discussed last month, the easiest and most secure way to do this is probably with xinetd. Setting up Xinetd There's not much involved in setting up a server with xinetd. All we're going to do is add the file my_php_server, shown in Listing 7, to the /etc/xinetd.d/ directory (though the process for adding a server to xinetd may be a little different for you depending on your system). Listing 7 service my_php_server { port server user socket_type protocol #only_from }
= = = = = =
64401 /path/to/server.php root stream tcp 10.0.0.4
There are a number of xinetd directives besides the ones shown here that you may want to research. An interesting xinetd directive that we've commented out here is only_from. only_from will limit the incoming connections to a specific IP range. Limiting connections to the address of your client machine, for example, would be a very applicable security measure. Now that we've added our server definition to xinetd.conf we have to restart xinetd. To do that, type service xinetd restart or, if that doesn't work, /etc/rc.d/init.d/xinetd restart should do the trick. Let's test it out a little. Follow the interaction shown in Listing 8. If you see something similar, congratulations, you're almost done! If not, here are some troubleshooting tips August 2003 · PHP Architect · www.phparch.com
that will hopefully speed you along to the final step: • If you're having trouble Telneting to your server, be sure that server.php executes properly when executed by itself from the command line. • If the script executes as expected, but you still can't telnet to the server, make sure that you restarted xinetd. • If it still fails, check to see if you are running a firewall - you could very well be blocking port 64401. You'll need to unblock it (and probably restart your firewall software) before any connections you try to make will get through. Putting it all together If our tests so far are any indication, everything should work fine. Let's find out by loading up client.php in a browser, choosing a server, and submitting the form. Hopefully, after a few seconds, you'll get confirmation that a new account was created. Now all you have to do is copy the server script and xinetd config file to your other web servers and you should be good to go. One of the most frustrating bugs I ran into when I was first designing this system was when I set up a server to run on the same machine as my client. What happened was that every time I tried to create an account, the connection would break. I didn't realize until after hours of fruitless walkthroughs of my code that the reason my connection broke was because the account creation utilities restarted Apache upon creating an account and would thus terminate my client script. To get around this problem, I made a work-inprogress HTML page that consisted only of a 30-second pause and a work status graphic. Once the client sent the customer's information to the server, the client would redirect the browser to the work-in-progress page. This work-in-progress page required no interaction with the server and therefore wasn't impacted when the server restarted. After the 30 seconds, the work-in-progress page would direct the browser back to the client script which could request the status of the account without fear of being broken off by Apache. Additional Ideas An interesting off-shoot of this technique would be to Listing 8 [root@ns1 root]# telnet 127.0.0.1 64401 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. PHP|ARCH phparch.com Corporate
[email protected] testuser testpw Account created successfully!
36
FEATURES develop a system that took given information and created the user's account on the optimal server automatically without requiring administrator interaction. For example, if your three servers ran Linux, FreeBSD, and Windows, your script might check to see which type of account your customer wanted and then automatically start the install process on the correct server. Another idea would be a sort of load-balancing mechanism where you monitor your web servers' load averages (or disk space usage, or free memory, etc.) and install the customer's account on the web server with the best stats. In fact, this is so interesting, that our next task will be to build a load average monitoring system!
"...cron is a standard unix utility that runs programs according to a predefined schedule..."
Case Study 2: Load Average Monitoring System We'll pretend now that we want to monitor our three web servers' load averages. We'll develop a client-server system similar to our accountcreation utility to provide a remote centralized location with access to our web servers' vital stats. We'd like to have a client script that can query our servers for their current load averages and then store the information in
Sockets: Part 2 a database so that we can later summon it up for averaging, graphing, and additional reporting. Unfortunately (or fortunately, depending on how much you like to code), servers don't just send out their load averages anytime someone wants them - we'll have to write a script to return this info to us when we request it. This should be starting to sound a little like our account creation utility. Here's our plan: we'll write a client script to send requests to our servers for data. We'll use xinetd on our servers to accept the requests and start the server scripts. The server scripts will each print their system's load average to STDOUT, which will be read by the client script and inserted into a MySQL database. Storing our load averages in a database lets us easily pull them up whenever we want. It would, for example, be a very simple matter to create a graph of the load averages for all of our servers. Listing 9 shows the SQL code for creating the table we'll be using to store our load averages. Listing 9 CREATE TABLE systemlog ( ServerName varchar(200) NOT NULL default '', Load varchar(10) NOT NULL default '' ) TYPE=MyISAM;
The Client Script Our client script will need to record data on a regular schedule which means that it would be pretty ineffi-
Listing 10 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
#!/usr/bin/php -q Array ('Name' => 'Server 1', 'IP' => '10.0.0.1', 'Po 'Server 2' => Array ('Name' => 'Server 2', 'IP' => '10.0.0.2', 'Port' => 64402) 'Server 3' => Array ('Name' => 'Server 3', 'IP' => '10.0.0.3', 'Port' => 64402) ); foreach ($ServerList as $Server) { $ServerConnection = fsockopen ($Server ['IP'], $Server ['Port'], $ErrNu, $ErrStr); if (!$ServerConnection ) { PrintLn ("$ErrStr ($ErrNu)" ); } else { while (!feof ($ServerConnection)) { $ServerLoad = fgets ($ServerConnection , 128); } $TimeStamp = time (); $Query = "INSERT INTO ServerLog (Load, ServerName, Time) VALUES (\"$ServerLoad\", \"$Server[Name]\", \"$TimeStamp\")"; mysql_query ($Query) or die ("Query failed"); fclose ($ServerConnection ); } } ?>
August 2003 · PHP Architect · www.phparch.com
37
FEATURES
Sockets: Part 2
cient for us to execute it with a browser as we did with the account creation scripts. If you're thinking that we can use cron, you're right! cron is a standard unix utility that runs programs according to a predefined schedule - we can use it to schedule the execution of our client script. This time, both our client script and our server scripts will need to be executed from the command line. The client script is shown in Listing 10 (see previous page). In order to get this script running, remember to chmod it as shown below: [root@ns1 root]# chmod 755 load_client.php
Fundamentally, there's little difference between this client and the account-creation client we wrote in Case Study 1. We start off by connecting to MySQL in preparation for inserting the servers' load data. Next we have a list of our servers and their addresses over which the foreach loop iterates. Inside the loop we open a connection to a server. If the connection works, we store the output in the variable $ServerLoad and then insert it along with the server's name and a timestamp into our MySQL table. We haven't seen anything too terribly difficult so far, it's just been a command line PHP file, some socket stuff, and a few MySQL queries. Let's get cron working now. Setting up cron Cron's pretty easy to configure. All we have to do to get load_client.php running on a set schedule is to add one line to our cron schedule. To do this, run the command shown below: [root@ns1 root]# crontab -u root -e
Note that I've chosen to use the root user's crontab file (that's what the -u root part of the command Listing 11 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
means). The best user to run under, however, would be the owner of the load_client.php script, which in your case might be a different user. Inside the crontab file, you might see a few lines already there. We'll insert a new one at the top. To start entering our line, type i and copy the line shown below. * * * * * /path/to/load_client.php
To save the crontab entry, hit the escape key, type :wq, and hit the return key. A complete cron tutorial is out of the scope of this article, but Figure 3 shows a breakdown of what our cron entry meant. An asterisk represents every value, so we are executing load_client.php every minute of every hour, every day of every month on every day of the week. That was a mouthful. After the five asterisks is the command to run. Obviously, we are calling load_client.php. For more details, run the command man cron on the command line to open up the cron manual. Well, now we've got our script running every minute, but it's got no server to connect to! Let's hurry and build our server now.
"...we'll use the uptime utility which returns a number of system stats including the load averages."
The Server Script Our server needs to return the current load average. To do this, we'll use the uptime utility which returns a number of system stats including the load averages. Since we only want the current system load average, we'll have to extract it from the rest of the data. A standard uptime call returns the following: [root@ns1 root]# uptime 12:53pm up 18 days, 10:55,
#!/usr/bin/php -q
2 users,
load average: 0.58, 0.58, 0.53
We see that there are three load average values. They represent the load averages of the last minute (0.58), last five minutes (0.58), and last 15 minutes (0.53). We just want the average for the last minute. We'll strip it out with the operations shown on lines 6, 7, and 8 in the server code in Listing 11.
$ServerLoad = explode (': ', trim (shell_exec ('uptime'))); $ServerLoad = explode (', ', $ServerLoad [1]); $CurrentServerLoad = $ServerLoad [0]; fputs (STDOUT, $CurrentServerLoad ); fclose (STDIN); fclose (STDOUT); exit (); ?>
Figure 3 minutes
hours
days of month
months
days of week
command
*
*
*
*
*
/path/to/load client.php
August 2003 · PHP Architect · www.phparch.com
38
FEATURES
Sockets: Part 2
Up until line six, there's nothing we haven't seen before. Starting on line six, we read in the result of the uptime command and strip out the appropriate load average. We then send the load average to STDOUT, close the streams, and exit the script. Don't forget to chmod load_server.php as shown below. [root@ns1 root]# chmod 755 load_server.php
As always, to make sure it's working, we'll do a test run. First, we'll run uptime to see what the current load is, and then immediately after, we'll run load_server.php to see how well they match (in reality, we'd run it a couple times to be certain, but for space-saving purposes, we've cut it down here). [root@ns1 root]# uptime; ./load_server.php 2:45pm up 19 days, 12:47, 1 user, load average: 0.55, 0.41, 0.41 0.55[root@ns1 root]
As you can see, it seems to work fine - the minute load average of the uptime command (0.55) matches the load average output by load_server.php (0.55) exactly. Now we need to set up xinetd to accept incoming connections. To do this, we add the file shown in Listing 12 to our /etc/xinet.d/ directory (this may be different for you depending on your system). Make sure you remember to restart xinetd after adding your config file. Alrighty, we should be good to go here. The cron job is running load_client.php every minute, and load_server.php should be responding. I've installed it on just one server here, but it'll give us a good indication of how well everything's working. Listing 13 shows what we've collected in our database so far. Looks good! We've collected nine pieces of data and we can tell from the time stamps that they're spaced about 60 seconds apart. If you don't get similar results, see if the troubleshooting tips from Case Study 1 can help. Additional Ideas There are a number of useful applications we can build on top of what we've written in Case Study 2. Firstly, we might add a mechanism to delete rows after a cerListing 12 1 service loadwatch 2 { 3 disable 4 port 5 socket_type 6 protocol 7 wait 8 user 9 server 10 }
= no = 64402 = stream = tcp = no = root = /path/to/load_server.php
August 2003 · PHP Architect · www.phparch.com
tain amount of time (because, data would accumulate very quickly - over 10,000 rows in one week for one server alone!). Then, we could combine it with an account creation system as suggested in Case Study 1 to help accurately distribute customer accounts to the most optimal systems. Some other ideas would be to use the data harvested with this technique to display graphs of our (hopefully very low) server loads to prospective customers. We could edit load_client.php to e-mail us whenever the load reaches a certain level, to initiate the shutdown of non-critical services, or even to restart the server machine itself. Conclusion I hope this article demonstrated a way to use PHP you hadn't seen before. We covered a few *nix utilities usually not associated with PHP and we took a look at PHP shell scripting. There are undoubtedly countless other applications besides the two we examined today. If you come up with something else or if you run into any roadblocks, post a message in this article's forum at http://www.phparch.com/discuss and I'll try to get back to you. Good luck and happy coding!
About The Author
?>
Eugene Otto is an undergrad at the University of Virginia majoring in Computer Engineering. He applied the techniques covered in this article to a suite of tools he built for a small web hosting company. He can be reached at
[email protected].
Click HERE To Discuss This Article http://www.phparch.com/discuss/viewforum.php?f=39
Listing 13 mysql> select * from ServerLog; +---------+------------+------------+----+ | LoadAvg | ServerName | Time | id | +---------+------------+------------+----+ | 0.34 | Server 1 | 1058556961 | 1 | | 0.12 | Server 1 | 1058557021 | 2 | | 0.37 | Server 1 | 1058557080 | 3 | | 0.13 | Server 1 | 1058557141 | 4 | | 0.47 | Server 1 | 1058557201 | 5 | | 0.17 | Server 1 | 1058557260 | 6 | | 0.61 | Server 1 | 1058557321 | 7 | | 0.73 | Server 1 | 1058557381 | 8 | | 0.30 | Server 1 | 1058557440 | 9 | +---------+------------+------------+----+ 9 rows in set (0.00 sec)
39
Can’t stop thinking about PHP?
Write for us! Visit us at http://www.phparch.com/writeforus.php
Click HERE for our “Author Guidelines”
Grokking cURL
F E A T U R E
By Peter James
L
ast month, I wrote about web automation. The tool that I wrote, affectionately named Scout, used curl as its HTTP engine. I included a brief introduction to curl (correctly written as cURL) in that article, but it left me thinking that a more formal treatment was necessary. I was amazed to find that php|architect had yet to run an article dealing with this indispensable tool. I know, I know. In PHP 4.3, the brand spanking new Streams API was unleashed, which, among other things, means that anything that remotely resembles a data stream can now be controlled using ordinary file functions. Previous to this, curl was the only way to do most protocols, unless you got down and dirty in raw sockets. Because of the ease of use of file functions, some people might be tempted jump on the wagon and say that curl is no longer useful. Not true. Not even a little. There are plenty of things that curl still offers that the file functions can’t. Things like: • • • •
cookies HTTP POST HTTP Authentication redirection (via Location: headers)
In this article, I’m going to introduce curl by example August 2003 · PHP Architect · www.phparch.com
in all of its humble glory The article’s main focus will be on HTTP transfers, but we’ll look at some of the other supported protocols as well, including DICT and FTP. Let’s get started. What is curl? Basically, curl is an Internet agent. It is a general purpose client, with support for many different protocols, including HTTP, HTTPS, FTP, FTPS, LDAP, DICT, FILE and TELNET. This is pretty impressive in itself, but curl also makes things easy to do. Handling cookies is trivial. Doing HTTP file uploads are a joke. Using SSL or TLS is ridiculously simple. What is curl? curl is awesome. The base libcurl library and the command-line tool are maintained by Daniel Stenberg, who also happened to write them. Sterling Hughes wrote and maintains the PHP wrapper to libcurl. In my humble opinion, curl is one of the top ten most useful extensions in PHP.
REQUIREMENTS PHP Version: php 4.1+ O/S: Any Additional Software: cURL extension Code Directory: cURL
41
FEATURES
Grokking cURL
Getting started with curl Let’s start off with a simple HTTP example. Take a look at Listing 1. This is about the simplest curl session you can make. Let’s walk through it. Listing 1
First, we have to initialize the curl session using curl_init(). This is similar to calling fopen() on a file, and returns a resource handle that is needed for all other curl calls. The parameter to curl_init() is a URL, and is actually optional. If it is omitted, we can specify it later by setting a specific option – but we’ll deal with that in a moment. Our next step is to execute the curl session using curl_exec(). This simply (in this case) performs the HTTP request, and outputs the results. curl_exec() returns a boolean indicating success or failure. As we’ll see in a moment, the behavior of this function can be changed by setting a number of options. The last thing to do is close the curl session using curl_close(). This is like any other standard close function, and just frees up the resources used by the session. So that’s that. You’ve just joined the curling club. The script operating on the other end of this request is shown below:
You can see the output of Listing 1 below. should see something similar.
You
petej@www $ php -f listing1.php Hello, 192.168.2.100
Getting fancy Right now, our curl_exec() call just dumps out the output from the remote URL directly to the browser Sure, you could surround it with output buffering functions, but why? All you need to do is set an option or two, and you can tap into a massive amount of ability. Have a look at Listing 2. This is basically the same script as Listing 1, but contains a number of optional settings. Let’s run through it. This time we opted not to put the target URL in the curl_init() call; instead, it’s set with the CURLOPT_URL option, using the curl_setopt() function. curl_setopt() is probably the most powerful August 2003 · PHP Architect · www.phparch.com
function in the curl extension. Its manual page on http://www.php.net/ contains dozens of options, many of which are sparsely documented. The user comments on php.net are great, and the libcurl documentation at http://curl.haxx.se/ sometimes contains that extra little bit of understanding that is needed. Let’s look at the options we set in Listing 2. We already mentioned CURLOPT_URL. This sets the target for the session, and can be used instead of specifying it in the curl_init() call. CURLOPT_RETURNTRANSFER is a flag that tells the curl extension to return the output from the curl_exec() call, rather than dump it straight out to the browser. This means that curl_exec() no longer returns a boolean on a successful call, but it does still return false on a failed call. Note that if you wish to output the results from curl_exec() to a file directly, you can do so by using CURLOPT_FILE, instead. CURLOPT_FOLLOWLOCATION is a very valuable flag for letting curl know that we want the curl session to follow any “Location:” headers. There are lots of situations where this would be extremely valuable. If you notice, we actually slightly changed the URL in our call. We dropped the trailing forward slash. This will cause a redirect response from the server, strongly suggesting that we should get the document we’re looking for at http://php.shaman.ca/curl/, not http://php.shaman.ca/curl. If we don’t set CURLOPT_FOLLOWLOCATION, our curl session will end with the redirect response, and the document we thought we were fetching will never see the light of day (try it – just comment out this line). CURLOPT_FOLLOWLOCATION handles “Location:” headers seamlessly, and, in my opinion, is invaluable. CURLOPT_HEADER is another flag that tells curl that we want to output the response headers along with our session results. This can be extremely useful, for example, when debugging your file download scripts. You can make curl simulate the browser, and see what MIME type is being sent for your data. Another use for this option is grabbing the correct MIME type when tunneling calls from one server to another. CURLOPT_VERBOSE is a very handy little option that lives up to its name. By setting this option you can see Listing 2 1 2 3 4
5 6 7 8 9 10 11 12 13 ?>
$ch = curl_init(); curl_setopt($ch, CURLOPT_URL, 'http://php.shaman.ca/curl' ); curl_setopt($ch, CURLOPT_RETURNTRANSFER , 1); curl_setopt($ch, CURLOPT_FOLLOWLOCATION , 1); curl_setopt($ch, CURLOPT_HEADER, 1); curl_setopt($ch, CURLOPT_VERBOSE, 1); $output = curl_exec($ch); curl_close($ch); print "CURL OUTPUT:\n{$output}\n";
42
FEATURES exactly what your curl session is thinking and doing throughout its life. This is obviously invaluable for debugging, but beware, as it dumps all informational output to the browser directly. The last change in Listing 2 is that we are now grabbing the output from the curl_exec() call, and outputting it how we want to output it. Have a look at Listing 3 for the output of Listing 2. Listing 3 is pretty long, especially given what the output of Listing 1 was, and could probably do with some explanation. All of the lines prefixed by “>”, “<”, or “*” are output generated by CURLOPT_VERBOSE. The “>” lines are the request headers sent to the server. The “<” lines are the response headers sent to curl by the server. The “*” lines are general information messages from the curl session. The output of the curl session can be seen after the “CURL OUTPUT:” line, and includes the response headers from both requests (both the original and the Listing 3 cwd: /usr/local/www/data/php.shaman.ca/curl petej@www $ php -f listing2.php * About to connect() to php.shaman.ca:80 * Connected to php.shaman.ca (192.168.2.100) port 80 > GET /curl HTTP/1.1 Host: php.shaman.ca Pragma: no-cache Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */* < HTTP/1.1 301 Moved Permanently < Date: Mon, 28 Jul 2003 05:34:23 GMT < Server: Apache/1.3.27 (Unix) PHP/4.3.3RC1 < Location: http://php.shaman.ca/curl/ < Transfer-Encoding: chunked < Content-Type: text/html; charset=iso-8859-1 * Follow to new URL: http://php.shaman.ca/curl/ * Connection #0 left intact * Follows Location: to new URL: 'http://php.shaman.ca/curl/' * Re-using existing connection! (#0) * Connected to php.shaman.ca (192.168.2.100) port 80 > GET /curl/ HTTP/1.1 Host: php.shaman.ca Pragma: no-cache Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */* < HTTP/1.1 200 OK < Date: Mon, 28 Jul 2003 05:34:23 GMT < Server: Apache/1.3.27 (Unix) PHP/4.3.3RC1 < X-Powered-By: PHP/4.3.3RC1 < Transfer-Encoding: chunked < Content-Type: text/html * Connection #0 left intact * Closing connection #0 CURL OUTPUT: HTTP/1.1 301 Moved Permanently Date: Mon, 28 Jul 2003 05:34:23 GMT Server: Apache/1.3.27 (Unix) PHP/4.3.3RC1 Location: http://php.shaman.ca/curl/ Transfer-Encoding: chunked Content-Type: text/html; charset=iso-8859-1 HTTP/1.1 200 OK Date: Mon, 28 Jul 2003 05:34:23 GMT Server: Apache/1.3.27 (Unix) PHP/4.3.3RC1 X-Powered-By: PHP/4.3.3RC1 Transfer-Encoding: chunked Content-Type: text/html Hello, 192.168.2.100
August 2003 · PHP Architect · www.phparch.com
Grokking cURL redirect). As you can see from the output, as well as the CURLOPT_VERBOSE output in the previous lines, a redirect was followed by curl. In the end, the exact same script output was generated, but we’ve now got a boatload of debugging information if we need it. I encourage you to play around with this example Taking it even further Although, in my opinion, we’ve already covered some pretty cool stuff, we’ve barely scratched curl’s surface. Let’s add some more complexity, and see what sort of trouble we can get into! Listing 4 shows our new script, which handles cookies, HTTP basic authentication, HTTP POST, HTTP GET, and multipart form transfers. Let’s run through it. First, we set initialize the curl session. Note that we’re now targetting the protected directory. This directory contains an .htaccess file specifying HTTP Basic Authentication. The contents of this .htaccess file are shown below:
"What this means is that the protected directory requires a username and password to be given before anything in that directory can be served by Apache."
AuthType Basic AuthName “php|architect test site” AuthUserFile /usr/local/www/data/php.shaman.ca/curl/protected/.htpasswd Require valid-user
What this means is that the protected directory requires a username and password to be given before anything in that directory can be served by Apache. In our case, that username and password is stored in an .htpasswd file in the same directory. You can make this .htpasswd file with the following command (in Unix): htpasswd -c -b .htpasswd testuser testpass
For more information on htpasswd, see the man page. For more information on directory access control with .htaccess files, see the Apache web site at http://www.apache.org/. Let’s get back to Listing 4. Now that we’ve specified our target, we can get down to the business of setting options again. We first set the CURLOPT_RETURNTRANSFER option, so we can get the output of the session in a variable. We then set the username and password to use for the site with the CURLOPT_USERPWD option. This option takes a value in the form of username:password. The next two options set up the curl session to handle cookies. The CURLOPT_COOKIEJAR is where cookies should be stored, and CURLOPT_COOKIEFILE
43
FEATURES specifies from where cookies should be retrieved. These are generally the same file, but you may have reason to differentiate. NOTE: The file that you specify in the CURLOPT_COOKIEJAR and CURLOPT_COOKIEFILE options must exist prior to use. If it does not exist, the cookies you try to set will fail silently. This can be the cause of many a headache.
The last option that we set in this first session is CURLOPT_POSTFIELDS. This option allows you to specify variables to be submitted to the host via HTTP POST. If this option is set to a string of name-value pairs separated by ampersands, it is sent to the server as a normal application/x-www-form-urlencoded POST. If, as in our example, this option is specified as an associative array, then it is sent to the server as a multipart/form-data POST. You might know that in order to upload files from your web pages, you must specify the enctype attribute of the form to be multipart/form-data. This may suggest, then, that we could upload a file using the associative array, which is true. You can see in Listing 4 that we’ve set two variables in the array. var1 is just a basic POST variable, and contains “data1”. var2, however, is different. By prefixing the value of a variable in this array with the “@” symbol, we tell curl to use the contents of that file as the variable’s data. If you sneak ahead, and look at the output of this script, below, you can see that it uploads the Listing 4 file to the server. Pretty sweet. Finally, we execute the curl session, close the handle, and output the results. The reason that there are two
Grokking cURL sessions in this script is so that we can demonstrate the cookie handling features of curl. Listing 5, which we’ll look at in a moment, sets a cookie on the first request, and displays it in the second request. Let’s take a look at the second half of Listing 4 now. Our second request is very simple. The big difference is that we are no longer specifying the CURLOPT_USERPWD option; instead, we set the username and password in the URL, just like we could in a browser. Curl handles this seamlessly. Next, you can see that we again set the CURLOPT_RETURNTRANSFER, CURLOPT_COOKIEJAR, and CURLOPT_COOKIEFILE options. Finally we execute the session, close the handle, and output the results. Listing 5 shows the script on the server that handled our request and produced the output. The only thing of note here is that we set the cookie using setcookie(), but give it an expiry of time() + 5. This means that our cookies will time out after 5 seconds, so each time you run the script you should see consistent output. If we set our cookies to timeout immediately, we wouldn’t see any cookie output; if we set our cookies to timeout farther in the future, subsequent requests would show cookie output in both sessions. You can see the output from Listing 4 below. petej@www $ php -f listing4.php FIRST CURL OUTPUT: - You uploaded a file called listing4.php in the var2 variable. It was 921 bytes in length. - POST variable submitted: var1=data1 SECOND CURL OUTPUT: - GET variable submitted: var3=data3 - COOKIE variable retrieved: monster=I love cookies
One thing to note about Listing 4 is that we had to
Listing 4 1 'data1', 9 'var2' => '@listing4.php')); 10 $output = curl_exec($ch); 11 curl_close($ch); 12 print "FIRST CURL OUTPUT: \n{$output}\n"; 13 14 $ch = curl_init('http://testuser:
[email protected]/curl/protected/?var3=data3' 15 curl_setopt($ch, CURLOPT_RETURNTRANSFER , 1); 16 curl_setopt($ch, CURLOPT_COOKIEJAR , '/var/tmp/phpa_cookies.txt' ); 17 curl_setopt($ch, CURLOPT_COOKIEFILE, '/var/tmp/phpa_cookies.txt' ); 18 $output = curl_exec($ch); 19 curl_close($ch); 20 print "SECOND CURL OUTPUT: \n{$output}\n"; 21 22 ?>
August 2003 · PHP Architect · www.phparch.com
44
FEATURES use two completely different curl sessions to do two requests. Not doing this will yield unexpected results at best, and completely fail at worst. This has been a pain for many developers for a long time, so you may be happy to know that the CVS version of PHP5 finally has support for multi-session handles. Yet another thing to look forward to in PHP5. Meta-what? Have a look at Listing 6, and the output in Listing 7. This example is very similar to Listing 2, but notice the call to curl_getinfo(). This function returns an associative array of meta data about our curl session. We can use this for debugging, alerting users, keeping track of stats, or just showing off to our friends. Since the items returned here are well documented in the online PHP manual, we’ll just look at the most interesting ones. Starting at the top of the list under “CURL INFO OUTPUT:” in Listing 7, we see that the first item is “url”. This is the effective URL that the session used, including any GET variables that were passed in. Note that “effective URL” means the URL that was used in the last transfer, after any redirects. Thus you’ll notice that the “url” item contains the trailing slash, where our originally requested URL did not. The “http_code” item contains the last HTTP response code. A 200 indicates a successful request. Note that prior to this 200 we ran into a 301 (redirect), but this is not mentioned here. The “http_code” is useful to programmatically tell if your HTTP session found
Grokking cURL a live page or not. The last entry I want to mention is “total_time”, which contains the total time taken for the last transfer. This can be useful for tracking your system performance. Something different HTTP is easy. HTTPS is just as easy; all it requires is a different scheme on the URL. Let’s do something other than HTTP. Listing 8 shows a simple DICT protocol request for the word “mitigate”. DICT is a dictionary server protocol, described in very dry detail in RFC 2229 (ftp://ftp.isi.edu/in-notes/rfc2229.txt). Basically, what we’re doing is querying a DICT server for any definiListing 6 1
Listing 5 1 $value) 6 { 7 print "- You uploaded a file called {$_FILES[$key]['name']}" 8 . " in the {$key} variable. It was {$_FILES[$key]['size']}" 9 . " bytes in length. \n"; 10 } 11 12 foreach ($_GET as $key=>$value) 13 { 14 $output = print_r($value, true); 15 print "- GET variable submitted: {$key}={$output}\n"; 16 } 17 18 foreach ($_POST as $key=>$value) 19 { 20 $output = print_r($value, true); 21 print "- POST variable submitted: {$key}={$output}\n"; 22 } 23 24 foreach ($_COOKIE as $key=>$value) 25 { 26 $output = print_r($value, true); 27 print "- COOKIE variable retrieved: {$key}={$output}\n"; 28 } 29 30 ?>
August 2003 · PHP Architect · www.phparch.com
45
FEATURES tions for the word “mitigate”. Listing 9 shows the response. Interesting. That was pretty easy, hey? Now let’s look at doing FTP with curl. Listing 10 (in this month’s package) performs four different curl sessions. One to list the remote directory’s contents, one Listing 7
Grokking cURL to upload a file, another to list the remote directory’s contents after the upload and delete the uploaded file, and one to show that the delete worked. Let’s go through the code. By now, I’m going to assume you’re familiar with standard flow of a curl session, so I’ll just comment on the things that are new.
petej@www $ php -f listing6.php CURL OUTPUT: HTTP/1.1 301 Moved Permanently Date: Tue, 29 Jul 2003 08:20:18 GMT Server: Apache/1.3.27 (Unix) PHP/4.3.3RC1 Location: http://php.shaman.ca/curl/ Transfer-Encoding: chunked Content-Type: text/html; charset=iso-8859-1 HTTP/1.1 200 OK Date: Tue, 29 Jul 2003 08:20:18 GMT Server: Apache/1.3.27 (Unix) PHP/4.3.3RC1 X-Powered-By: PHP/4.3.3RC1 Transfer-Encoding: chunked Content-Type: text/html Hello, 192.168.2.100 CURL INFO OUTPUT: Array ( [url] => http://php.shaman.ca/curl/ [content_type] => text/html [http_code] => 200 [header_size] => 405 [request_size] => 255 [filetime] => -1 [ssl_verify_result] => 0 [redirect_count] => 1 [total_time] => 0.004 [namelookup_time] => 0 [connect_time] => 0 [pretransfer_time] => 0.001 [size_upload] => 0 [size_download] => 21 [speed_download] => 21 [speed_upload] => 0 [download_content_length] => 0 [upload_content_length] => 0 [starttransfer_time] => 0.004 [redirect_time] => 0.004 )
NOTE: The FTP server I used in my examples was only set up while I was writing this article. I don’t generally like to run an FTP server, so you will not be able to run this script against my server. First, since we’ll be connecting to the same server four times, we set up some variables. You’ll need to change these to reflect your particular setup. Next, we start our first curl session by initializing the connection to the FTP server and navigating to the ftp/ directory. We then set the username and password for our connection. Note that this is the same option that we specified for our HTTP Basic Authentication back in Listing 4. Also note that we could have specified the Listing 8 1
Listing 9 petej@www $ php -f listing8.php CURL OUTPUT: 220 pan.alephnull.com dictd 1.8.0/rf on Linux 2.4.18-14
<[email protected]> 250 ok 150 1 definitions retrieved 151 "Mitigate" web1913 "Webster's Revised Unabridged Dictionary (1913)" Mitigate \Mit"i*gate\, v. t. [imp. & p. p. {Mitigated}; p. pr. & vb. n. {Mitigating}.] [L. mitigatus, p. p. of mitigare to soften, mitigate; mitis mild, soft + the root of agere to do, drive.] 1. To make less severe, intense, harsh, rigorous, painful, etc.; to soften; to meliorate; to alleviate; to diminish; to lessen; as, to mitigate heat or cold; to mitigate grief. 2. To make mild and accessible; to mollify; -- applied to persons. [Obs.] This opinion . . . mitigated kings into companions. --Burke. Syn: To alleviate; assuage; allay. See {Alleviate}. . 250 ok [d/m/c = 1/0/20; 0.000r 0.000u 0.000s] 221 bye [d/m/c = 0/0/0; 0.000r 0.000u 0.000s]
August 2003 · PHP Architect · www.phparch.com
46
FEATURES username and password in the URL, just like we did in Wrapping up Listing 4. Although we’ve covered a lot of material, there are still Finally, we set the CURLOPT_FTPLISTONLY option, many stones left unturned. Perhaps these will materiwhich is an easy way to specify that curl should return alize into a future article. Things left to cover include: a listing of the current working directory on the FTP handling SSL certificates, specifying callbacks for hanserver. dling particular types of data, LDAP, and proxies. Now that we’ve set up the session, we execute it and I hope that this article has helped you see a use for output the results. You’ll notice here that we are check- curl in your day-to-day grind. I know that it’s made my ing for an error, which is something we haven’t done life easier on more than one occasion (including writing before. curl_errno() returns a zero if no error last month’s article!). ocurred, and the error code otherwise. If there was an If you have any comments or questions about what error, we replace our output with the error message. I’ve covered here, please drop a note in the forum for If you peek ahead at the output for Listing 10, shown this article. in Listing 11, you can see that there was an error, small though it may be. In our second session, we are doing a file upload. We first specify a file to upload ($infile), as well as the name to upload to on the server ($destination). We do the regular connection initialization, and specify this session to be verbose. This will help us see what’s going on on the backend, just for curiosity’s sake. About The Author ?> In order to specify a file upload to an FTP server, we Peter James is a freelance developer and writer living in Canada, and set the CURLOPT_UPLOAD option. This, in conjunction claims to be the editor-in-chief of php|architect. In his spare time he can with an open file handle to the file we want to upload be found chasing a 3-year-old around the house. You can reach Peter at (specified to CURLOPT_INFILE), will allow us to [email protected]. upload a file with next to no effort. Click HERE To Discuss This Article Now that the options for our second session are finhttp://www.phparch.com/discuss/viewforum.php?f=40 ished, we can execute it and see what we get. If you peek ahead again at Listing 12, you can see all of the debugging output provided by the Listing 11 CURLOPT_VERBOSE option. Again, the “>” lines petej@www $ php -f listing10.php CURL OUTPUT: are what curl sends to the server; the “<” lines are Error: No files found. the responses sent to curl by the server; and the to connect() to www.shaman.ca:21 “*” lines are general information messages about ** About Connected to www.shaman.ca (192.168.2.100) port 21 the session. Interesting. < 220 www.shaman.ca FTP server (Version 6.00LS) ready. Finally, our script lets us know that a file called > USER petej < 331 Password required for petej. “listing10.php” was uploaded to the FTP server > PASS password < 230 User petej logged in. successfully. * We have successfully logged in Let’s test that that really happened by > PWD doing another list on the server, shown in < 257 "/usr/home/petej" is current directory. path is '/usr/home/petej' session 3 of Listing 10. This session is exactly *> Entry CWD ftp the same as our first session, except < 250 CWD command successful. that we specify a CURLOPT_POSTQUOTE option. > EPSV < 229 Entering Extended Passive Mode (|||49293|) CURLOPT_POSTQUOTE allows you to give a series * About to connect() to www.shaman.ca:49293 of commands to curl that will be executed after * Connecting to www.shaman.ca (192.168.2.100) port 49293 * Connected the data stream with PASV! the normal session operations. In our case, the > TYPE I normal session operation is to list the current < 200 Type set to I. listing10.php working directory on the server. We’ve specified >< STOR 150 Opening BINARY mode data connection for 'listing10.php'. the command “DELE listing10.php” to execute < 226 Transfer complete. after our directory listing, so that we can clean up * Connection #0 left intact * Closing connection #0 after ourselves, but still see that we actually CURL OUTPUT: File listing10.php was successfully uploaded uploaded the file. Session 4 is exactly the same as our first session, CURL OUTPUT: and shows that the file we uploaded is now gone. listing10.php Easy, right? CURL OUTPUT: Error: No files found.
August 2003 · PHP Architect · www.phparch.com
47
Maintenance from The Outset F E A T U R E
By Graeme Foster
D
eveloping software is an ongoing process. The process should not be a simple case of jotting down some design, diving into the coding of the system and then leaving the users to make the best of the result. However, failed software projects are often guilty of failing to look to the future. If PHP is to make a successful transition into the enterprise arena, then as developers we need to ensure that the systems developed are robust. Maintenance can be placed under two broad categories, namely bug fixes and enhancements. Both of these categories need to be considered at the start of a project and not, as all too often is the case, tagged on to the system as an afterthought. The reality is that any complex system will have bugs in it. These will range from system failure to user error. From a developer’s perspective, these should both be treated as problems that need to be corrected. I don’t mean that the user is a problem, but rather that because they are experiencing an error situation there are problems with the design or the code. Finding different terms to categorize the error (the “It’s not an error, it’s a feature” debate) may placate the user for a while, but if you care about the system then you should care about its performance out there in the wild. If the users think it is buggy, clunky, and unresponsive then that is a system destined to die.
August 2003 · PHP Architect · www.phparch.com
User Feedback I spent many years supporting systems built and run within an organization and I have found that it is essential to get the users to explain the problem. The purpose of this is twofold; the first objective is to gather information so that I can replicate the error situation. The second is to give me a better understanding of how the user actually interacts with the system. Of course, as a developer I know how it was designed to be used, but users have a tendency to adapt the flow through a system to suit their own needs. Whilst talking to the user is beneficial it does have limitations: “So the system crashed, tell me what were you doing?” Immediately the user is on the defensive, “I didn’t do anything, I was just entering the data as normal…” It will often take much careful and painstaking querying before the actual flow of events may unfold. All too often the reality is that the user may not remember the exact steps they took, and anyway, how far back do REQUIREMENTS PHP Version: 4.3+ O/S: ANY Additional Software: Pear::DB (for the sample application) Code Directory: maint
48
FEATURES they need to go? The last screen, the last transaction, the last hour - just how far? The next issue to consider is that with systems designed for the Internet the user is out there but you may not be in a position to contact them directly. This means that for some applications it is essential to gather all the information required to ensure that the error situation can be faithfully duplicated. I believe that what is needed is a set of tools to be put in place that will monitor the performance of a system and record how the users of that system interact with it. Monitoring tools For any system there will be a number of aspects that can be monitored. Any form of monitoring will add an overhead to the responsiveness of the system. Thus, it will be necessary to be able to switch these tools on and off as required. The system administrator, whose job it will be to do this, is unlikely to be the developer and so it is necessary that such action can be performed with the minimum amount of fuss. The basic categories into which the monitoring tools will fall include: • Error Message • System Event • Session Record Error Message The error log will commit every error to a file allowing the developer to read the complete list of errors. This can be viewed online and summaries made available to the administrator. The errors need to be categorized, because it is essential to be able to differentiate between a serious error that is preventing the system from functioning and an error situation where the user has failed to complete a required field. This leads to one observation: why do I want to record errors where the form has been completed incorrectly? By recording this information and building some simple analysis tools of the log file it is possible for me to see if and where users are stumbling over the system. This could be as simple as counting the total occurrences of each error message and displaying them in descending order. Each error situation is unwanted traffic on the network. If I am able to reduce the number of errors, then the network traffic to transaction ratio will be reduced and so the overall responsiveness of the system will improve. System Event The system event will log all the interactions of the system with the server or servers. Typically, this will be database activity, although this could also include file access (although not the system event logging itself) and URL details. This will need different levels of sensitivity built into it. Consider the standard SQL comAugust 2003 · PHP Architect · www.phparch.com
Maintenance From The Outset mands; a select statement can pose little in the way of damage to the data, whilst the delete statement signals curtains for the data. This means that a system event at one level of sensitivity will need to be able to record who deleted the data and at another level log what was actually deleted. Turning on and off the logging of select statements is another feature that will need to be made available to the administrator. In a data intensive system this will result in a lot of logging, especially if snapshots of the data are taken, and will have a serious impact on the responsiveness of the system. However, a record of the database activity can help to explain the complex interactions that occur in a production environment and may provide the clue needed to stomp out a particularly stubborn bug. Maybe the problem is that file locking was not implemented properly in one area of the system. One user wants to modify a record on the modify screen. They should have sole access to the data! Someone else comes along issues a delete statement – and zap! The row is gone. Now the first user attempts their update. There was no match for the update but the SQL was valid so the user may not notice that the update didn’t even work. Three days later the error is raised as: “Whenever I update a client they disappear.” Well obviously the client doesn’t but what happened to the data? Session Record The session record is a historical log of the actions of each user on the site, mapping the path that they have taken through their interaction with the system. Normally a system is designed with an optimal path between two points. Unfortunately, no matter how obvious that path is to the designers, users tend to find another path through the system. The analysis of the path that users have taken can prompt more efficient design of the system as a whole. Also, a list of the most commonly visited pages and most commonly used actions can prompt redesign to improve the access to these parts of the system. A Proactive Approach Thinking about these issues from the start is important, because they will impact the design of the entire system. Take a moderate system with a couple of dozen classes: if each class has an average of eight fields that need to be validated, then that is about two hundred error messages. Attempting to add the code to log these error messages after they have been written will be tedious, and any monotonous job is an opportunity for bugs to enter the system. Putting the support code in from the beginning can not only make the logging easier but it can also make writing the error messages easier, and if it is easier to write the error messages then I am more likely to adopt that approach.
49
FEATURES
Maintenance From The Outset
The Initial Design To be able to realize the win-win situation we desire requires proper design. However, the reality is that deadlines require that the development effort be put into building the system and not the peripherals. The result is that frameworks tend to evolve over time. Given that deadlines are the reality I will just look at how the error logging can be implemented. This is a good start because it can actually make it easier for me to code the error messages. My first design decision is to pursue a class approach to the design. While PHP 4 doesn’t really support encapsulation, it does enable the required functions to be placed in a single area. This has a significant advantage if a framework is being developed in piecemeal fashion, because it makes it much easier to revisit. Figure 1: Class Diagram Fram ew ork
Installer
SevereError
<<Static>>
InstallD isplay
trol then it is possible for outside forces to change the value of that attribute directly. For example the following will change the value of the display attribute of an error object: $error->display = false;
On the other hand, if I give the display attribute private access control then the above snippet will cause an error. This is because it is only possible to change the value from within the object itself. So the only option available to me to change the value is to write an operation with public scope, which might be accomplished with the following function: function setDisplay($value) { this->display=$value; }
This will be an operation of the ErrorLog class and so the object itself now changes the value of display and it will be called as follows: $error->setDisplay(false);
ErrorLog
<<Static>>
Install
TestA pplication <<Static>>
PersonD isplay
<>
Pear::D B
D /B
The error logging consists of two classes (see Figure 1). I have placed the bulk of the error logging in the ErrorLog class, but the SevereError class is needed on the rare occasions that the error logging experiences an error itself. The remaining classes belong to the small application that I have written to illustrate how to use the logging class. For simplicity, the bulk of the application is in the PersonDisplay class, which will be used statically. I have also included a small installer program that will add the necessary database and tables for testing the application. All database access is done through the Pear::DB interface. Unfortunately, PHP4 doesn’t support the concept of private and public access control - but just wait and it will be there with PHP5. Access control is used to indicate if an attribute or operation is available from outside of an object. If an attribute has a public access con-
August 2003 · PHP Architect · www.phparch.com
This is the underlying facet of encapsulation, but it doesn’t really explain its importance. While there is a lot of debate concerning what really is required for a language to be an object-oriented language (rather than merely ‘object based’), I believe that encapsulation is the most important. The reason for this is that it is easy to master and yet it helps to create code that is maintainable. “And just how does it do that?” you may be wondering. Well since the only way to change the display attribute will be through the setDisplay() operation, I can place the code in there to ensure that display always contains valid data as follows: function setDisplay($value) { if (is_bool($value)) this->display=$value; else echo “setDisplay expects you to send a Boolean value”); }
This now means that it will always contain a Boolean value. Now three months down the road when I want to change the display attribute I’ll very quickly learn that setting it to “no” will not be acceptable. In short, it is possible to protect the data held in the ErrorLog object from my own foolishness. The design process, as well as identifying the functionality of a class, is about describing the interface to the class. The interface describes the ways in which a programmer can interact with a class. To make it easier
50
FEATURES
Maintenance From The Outset
to use a class, the interface should be, to coin a phrase, “as simple as possible, but no simpler”. By that I mean it should consist of one public operation for each function that can be performed by the class. To perform this identified functionality, additional operations may be required by the class, for use only by the class, so we’ll declare them as private to the class. Now while this is not supported by PHP, I still design my classes with access control in mind. I have designed the ErrorLog class to have a single functional capability, namely I want this class to be able to log errors. Within this it has two different ways in which it can log errors. One is to display the error within an HTML page, and the other is to send it to a file on the server. The ErrorLog class has just two public interfaces, as indicated by the + sign in the class diagram in Figure 2. Both are operators. One is the constructor to be used to set up the attributes, and the other is the logMsg() operator, which will be called whenever an error condition arises. The rest of the ErrorLog class consists of attributes and operations that are private to the class. That is, they are required by an object of the class to assist it in performing its responsibility, which is to log an error. These fall into three basic categories, which are: displaying an error in HTML, writing the error to a file, and trying to cope with an error situation, which may be beyond the scope of the system, for example a server failure. Figure 2: The cErrorLog class diagram
cErrorLog -Display: Boolean -PreError: String -PostError: String -File: Boolean -Path: String -LogFileName: String -Locking: Boolean -Retry: Integer -Pause: Integer -Ouch: cServerError -ErrorStatus: Integer +<> cErrorLog(): +logMsg(): -pDisplay(): -pWrite(): -pWriteFile(): -pReadCount(): -pWriteCount(): -pErrorLog(): -pOpenFile(): -pReadFormat(): -pAppendLine(): -pWriteToFile():
Public Interface The constructor receives many parameters; all of these are set to default values. This has been designed so that later, when I have time to revisit the class, the default values can be picked up from a preferences file rather than hard-coded in this fashion. This can be done by August 2003 · PHP Architect · www.phparch.com
defaulting the values to an integer value. The default value will be held in the constant.inc file and will set an identifier, say, GET_FROM_PREF_FILE to a unique negative number. If within the constructor the value is set to GET_FROM_PREF_FILE then it will get the value from the preferences file. The constructor also instantiates an object of the SevereError class. This is used if any logging fails. This object is created at the construction stage rather than when it is needed so that it is always available. A severe error may occur if there is a lack of available memory, at which time it is too late to create the object, so I grab the necessary memory from the start. function cErrorLog ( $fileName = “LogFile”, $Path = “log/”, $display = true, $file = true, $PreErrorTag = “”, $PostErrorTag = “”, $locking = true, $retry = 10, $pause = 500) { $this->LogFileName = $fileName; $this->Path = $Path; if (!file_exists($this->Path)) mkdir($this->Path); $this->Display = $display; $this->File = $file; $this->ouch = new cSevereError; $this->locking = $locking; $this->errorStatus = NO_ERROR; $this->retry = $retry; $this->pause = $pause; $this->PreError = $PreErrorTag; $this->PostError = $PostErrorTag; }
The second public interface is the logMsg() operation. This is really quite simple, delegating all the work to private operations. It is split into two parts - the display on the screen and writing to a log file. function logMsg ($msg, $level, $break=true) { if ($this->Display) $this->pDisplay ($msg, $break); if ($this->File) $this->pWrite($msg, $level); }
Displaying in the HTML Each Error log object has a Boolean attribute $Display that will indicate if the error message is to be written within the HTML. Two other attributes are used to help with this: $PreError and $PostError. These hold the HTML that will appear before and after the error message. The default values call for them to change the font color to red. One private operation, pDisplay() is required,
51
FEATURES and this prints the information to the screen. Along with the error message to display, this also receives an optional parameter, $break, which, if set to true, will add an HTML break tag. function pDisplay ($msg, $break=true) //Should be private { echo $this->PreError; echo $msg; echo $this->PostError; if ($break) echo “
”; }
Maintenance From The Outset Writing to a file The second option that logMsg() has is to write the error message out to a log file on the disk. Again, this is controlled by a Boolean attribute which is called $File. Directly related to this are two other attributes, namely $Path and $LogFileName, which are used to record the relative path and the name of the file. The $LogFileName attribute doesn’t include any extension; this is added later because the log file uses two interlinked files. These two files are a counter and the log file. I use the counter to keep track of the number of messages that have been logged and it holds a single number. The counter file is given the extension .ctr and the log file has the extension .log. The log file is an opportunity to gather additional
Figure 3
August 2003 · PHP Architect · www.phparch.com
52
FEATURES
Maintenance From The Outset
information; the display is used to place the error message on the screen, whereas the log file is to be used by me as a developer and maintainer of the system, which is why I want to record additional information. This is done in the pWrite() operation, which will receive the error message and the error level. It will then derive the IP address and the time that the error occurred. All of this will be compiled into a single error line. This error line along with a combination of the path and file name is then passed to a second operation pWriteFile(). This operation is responsible for coordinating the operations that access various files. First it calls pReadCount() to get the current log counter number, then after adding this to the start of the error message it calls pAppendLine() to add the line to the log file. Finally, it updates the counter by calling pWriteCount(). function pWrite($msg, $level) { $IPAddr = (string)$_ENV[‘REMOTE_ADDR’]; $timeStamp = getdate(); $file = $this->Path . $this->LogFileName; $line = $level . “\t” . $msg . “\t” . $IPAddr . “\t” . $timeStamp[“mday”] . “ “ . $timeStamp[“month”] . “ “ . $timeStamp[“year”] . “ “ . $timeStamp[“hours”] . “:” . $timeStamp[“minutes”] . “:” . $timeStamp[“seconds”] . “\n”; $theresult = $this->pWriteFile($file,$line); } function pWriteFile($file, $line) { $lineCount = $this->pReadCount($file . “.ctr”); $lineCount++; $line = $lineCount . “\t” . $line; $theresult = $this->pAppendLine($file . “.log”, $line); $this->pWriteCount($file . “.ctr”, $lineCount); return $theresult; }
I have added a number of other attributes and operations to manage the file access requirements: The attributes are $Locking
Used to determine if the file would be locked when written to.
$Retry
If the file is locked then how many attempts will be made to access it before it gives up.
$Pause
How long the program will pause between “retry” attempts.
The operations are pOpenFile()
Open the file, creating the file if indicated to do so by the $force parameter
pReadFormat()
Read a file using the format string, the default is a single line
pAppendLine()
Add a line to the end of the file
pWriteToFile()
Write the $msg to a file, locking before and then releasing the lock after if required.
Handling Errors The final task that the cErrorLog class needs to manage is when it encounters an error situation itself. Such a situation may occur if it can’t write to the log file, which can happen if the web-server hasn’t been given the correct access rights to the directory, or if the directory has run out of space. There are other more serious problems which, while it can’t guarantee to tell you about the error, it will try, just before the server enters meltdown. Oooh I hate those days. I created a cSevereError class to help out with such a situation and created a reference to it in the cErrorLog called $ouch. To achieve this I have used the PHP error_log() function which, through the use of constants, I have set up to write an error to the web server’s error log and also attempt to send me an email message. If both of those fail then it will attempt to display the whole message in the HTML file.
Continued on the next page...
August 2003 · PHP Architect · www.phparch.com
53
FEATURES
Maintenance From The Outset Continued from page 6...
if (!$email and ECHO_ON_FAILURE) { echo “A Severe Error has occurred. The server is not feeling well. Please contact the technical support and send them a copy of this page. Thank you, and sorry for any inconvenience that was caused.”; echo “<pre>” . $details . “”; } exit(-1); } } ?>
Testing the Logging Class I have included the full code for the Logging class with the source code package, it is held in the Logger directory, which in turn is held in the Classes directory. Remember this class has just two interface functions, the constructor and the logging function. That means that it is very easy to write a simple test for this class. This I have included in a file called unitTest.inc and it is in the root directory. Adding a few errorlogs
”; $log->logMsg(“FirstTest”,”Warning”); $log->logMsg(“Next Test”,”Error”); $log->logMsg(“Last Test, for now”,”Severe”); echo “
”; } ?>
I have created an index file that calls the addTestCases() function in the unitTest.inc file given below. This just creates an error log object and then passes the object to the addTestCases() function.
Fire up your web browser and you should get the following page: Adding a few errorlogs FirstTest Next Test Last Test, for now
August 2003 · PHP Architect · www.phparch.com
In the addTestCases() function, the $log->logMsg() operation is called three times resulting in the three messages being displayed in the browser. Note that because logMsg() is an operation of the cErrorLog class it requires the -> operator. In addition to displaying the message on the screen, it also should have written some information to a log file. In the root directory there will now be a directory called log. Inside this is a log of the error messages. 1 Warning FirstTest 127.0.0.1 8 July 2003 20:51:49 2 Error Next Test 127.0.0.1 8 July 2003 20:51:49 3 Severe Last Test, for now 127.0.0.1 8 July 2003 20:51:49
The log includes the error number, the level of the error, the error message, the IP address of the sending machine, and the date of the error. I ran this from the server and so I got the generic localhost IP address. The next test is to try and trigger a severe error. Change the properties of the log file such that the server can’t write to it and run the test again. If everything went well you should have been left with an event log with a full trace of the program calls. For me this gave an error message of –4003, which I looked up in the constants.inc file to find out that that error code related to a “Could not open file” error. Which makes some sense because I tried to open a file for writing when I only had reading privileges. Putting it to Work If you’ve gotten this far, then you have been able to test the logging program, but really it needs to be integrated into an application. For this I have written a small – in fact it couldn’t really get much smaller – application. It will store a person’s name to a database; the names are split into first and second name. That’s the only data. The available functions are list, view, add, edit, delete, and find. List and View If you point your browser to index.php it should help you to install the required database and populate the table with some test data. It will then list all the names in a table. From there you can view individual names and then go back to the list page. Within those functions there is little scope for error so let’s move on to the functions that modify the data on the table. Add and Edit These two functions share the same validation code and there are three rules: • The first name must not be blank • Neither the first name nor the second name can include a number
54
FEATURES
Maintenance From The Outset
So with that in mind let’s break some rules… I push the Add button and then, leaving the given name blank I entered a number in the family name. Figure 3 (page 53) shows the errors being displayed, and the two errors have also been entered in the log file. The log file for these errors has been placed in a log directory in the Display directory. In the log file both of these errors have been categorized as validation errors. Delete The delete function can only be performed if you are logged on. However, I have added an error in the code such that the delete button appears if you either view or edit a person. So view one of the persons and then try to delete them. The message will tell you that you need to be logged on. So try and log on. Without the user name and password you can’t log on and so another error will be created. Now try and log on with the username of “Me” and the Password of “Please”, ignoring the quotes but capitalizing the words as shown. Assuming that you managed to log on, you will now be asked if you wanted to delete the person. I don’t really - I just want to generate some errors! So I will now click on ‘list’ and then try the find function.
Find The find will help to locate some people. If there are no matches then it will display an error, which I can easily generate by entering the search string of %%^^$. Simple analysis of the errors Now that I have generated some errors I want to do a quick analysis. So I can open the log file, select it all, and then copy it into a spreadsheet. First I added a heading and then sorted the total by type. I added some subtotals and what I got is listed in Table 1. Eight errors in total - five of which were authorisation, three of which were stating, “You need to be logged on before you can delete a person”. In theory this error should not arise because the delete button should have been suppressed unless the user was logged on. Time to search out that anomaly in the code. Obviously with the data in a spreadsheet it is easy to run different analyses on the data: for example I can sort it by IP address, or by message, to get a better picture of how it is being used. This can also be compiled week-on-week to give a picture of how the occurrence of errors changes over time.
“... the advantage of classes is that production code will continue to function even if there are substantial changes to the way the class works.”
Where Next In the above example I have shown how easy it is to log errors, once the logging framework has been estab-
Table 1: Error Analysis ID Type
Message
IP
3 Authorisation
You need to be logged on before you can delete a person.
127.0.0.1
08/07/2003 21:55
4 Authorisation
Sorry, but I can't log you on
127.0.0.1
08/07/2003 21:56
5 Authorisation
You need to be logged on before you can delete a person.
127.0.0.1
08/07/2003 21:56
6 Authorisation
Sorry, but I can't log you on
127.0.0.1
08/07/2003 21:57
7 Authorisation
You need to be logged on before you can delete a person.
127.0.0.1
08/07/2003 21:57
Authorisation Count 8 SQL
Date
5 There was no match for your search: %%^^$
127.0.0.1
SQL Count
08/07/2003 22:00 1
1 Validation
The given name cannot be blank
127.0.0.1
08/07/2003 21:47
2 Validation
The family name cannot include a number
127.0.0.1
08/07/2003 21:47
Validation Count
2
Grand Count
8
August 2003 · PHP Architect · www.phparch.com
55
FEATURES lished. This is the first step in establishing a framework to assist in the maintenance of a system. The next step will be to add classes that will log system action and will monitor user action. Another important feature that is missing is giving control to how the logging works to the administrator of the system. Here I would need to abstract the preferences into a new class and then allow the administrator to change these preferences through a web interface. These enhancements come under two basic categories: those that are an enhancement to the design and those that will affect the existing design. The latter are, unfortunately, more problematic because they affect the existing code and so require the testing to be performed for the code that is already in production. However the advantage of classes is that so long as the public interface doesn’t change to these classes, production code will continue to function even if there are substantial changes to the way the class works. So I can safely modify the cErrorLog class and so long as I don’t modify the public interface, that is the function name or the parameter list of the public functions – namely the constructor and logMsg() then the application(s) that I have written will still work. The logMsg() writes directly to the HTML file, which actually limits its potential value. Consider the case when I want to write the same error in two places on the screen, once at the top and then by the input field. I would need to call the operation a second time. This would then log the error message to the file twice, which now means that I have a problem with the integrity with the data in my log file. Of course I could create a second log object that didn’t write to the log file, but will I always remember to use the right object? A better approach would be not to write any HTML out directly but to return a formatted HTML string and then insert that string in the required places in the HTML document outside of the logging class. Implementing this change within the class is trivial, however because this impacts how the public interface works it will require changes to every place the logMsg() is called. This is possible, but is a tedious process, and the result of missing a change to one instance of the logMsg() call is that the error message will not be displayed on the screen. That is not an easy situation to identify during testing. Hopefully this explains how important it is to get the design correct at the beginning. There are two alternative approaches to this. The first
“Maintenance consists of a large percentage of the whole development process, typically 3040%.”
August 2003 · PHP Architect · www.phparch.com
Maintenance From The Outset is to have logMsg() both print the HTML and to return the value in a string so it can be placed in another location in the HTML page. The second is to create a new operation, say, msg() that will return the message (and log it) but not print any HTML. Finally I just want to say that the “Log On” feature in the application is not suitable for a production system, please don’t copy it, as I just threw it in so that I could very quickly add authorization errors. Conclusion Maintenance consists of a large percentage of the whole development process, typically 30-40%, and yet surprisingly little effort is put to maintaining a system before the system has been developed. Time can also be wasted trying to figure out how to fix a system if the necessary information is not available. That is time that is taken away from improving the system. Using an object oriented framework it is possible to ease the presentation of error messages because the formatting that is required could be generated automatically and consistently. At the same time the error messages can be sent to a special log file enabling easy analysis of the error messages. Extra classes can be added to the logging framework to provide additional information, making it easier to add improvements to the system and to understand how the system is being used. But unless this is added from the outset then adding a logging feature once the system has been developed will be both difficult and error prone. Performance is one of those words that means different things to different people. Here I use the term performance to refer to a holistic evaluation of the system. By this I mean that performance includes, but is not restricted to, responsiveness, ease of use, reliability and acceptance by the user of the system.
About The Author
?>
Currently, Graeme is working as a lecturer (Computer Science) at Sherubtse College, Bhutan. He has also worked in the UK, Papua New Guinea, and New Zealand. Graeme much prefers the Himalayan air to London back streets even if the electricity is much less reliable.
Click HERE To Discuss This Article http://www.phparch.com/discuss/viewforum.php?f=41
56
Embedding Assembler in PHP F E A T U R E
By Igor Gorelik
Overview Assembler is used for many purposes: optimizing program size and speed, low level hardware programming, and a lot of other capabilities that are difficult or impossible to implement in high level languages(HLL) [1]. Inline assembler allows you to use assembly language’s power within programs coded with HLL such as Pascal, C/C++ or Basic. For example, in MS Visual C++, assembler insertions looks like this: __asm{ mov eax, 0x01234567 mov edx, 0x89ABCDEF xor eax, edx }
The same code in Borland Pascal style looks like this: asm mov eax, $01234567 mov edx, $89ABCDEF xor eax, edx
mov eax, var1 ; eax = 5 add eax, 75 ; eax = 80 push eax call SomeFunctionName ; calling for SomeFunctionName function }
Where “SomeFunctionName” could be a Windows API function name for example. A few months ago I thought I’d try to implement inline assembler in PHP. I didn’t really have a practical need for it, but I thought that it could be interesting. I decided to try to make an inline assembler extension for PHP under Windows. Developing an assembler translator is not a trivial task, so I decided to lean on one of the open-source assemblers for help. I chose Flat Assembler(FASM) [2]. FASM is an open-source assembler compiler written in assembly language and is available under Windows and Linux. Some of FASM’s features include:
end;
REQUIREMENTS Moreover, the inline assembler allows you to use identifiers from the HLL’s “namespace” in assembler statements, like this: long var1 = 5; __asm{
August 2003 · PHP Architect · www.phparch.com
PHP Version: 4.x O/S: Windows Additional Software: FASM Code Directory: assembler
57
FEATURES
• • • •
Embedding Assembler in PHP
16 and 32 bit code MMX and FPU instructions set Binary, MZ, PE, COFF output file formats Trivial hardware and software requirements
Newer versions also support SSE, SSE2 and AMD 3DNow! instructions. FASM uses Intel assembly syntax. For other features see FASM’s manual. This article describes the full process of making an inline assembler PHP extension and examples of how it might be used. It’s assumed that the reader knows something about assembly language, PHP, PHP-extensions and the Win32 platform. Recompiling FASM The first thing I had to do is recompile FASM into a COFF(Common Object File Format) file so I could link it to my extension (written in C++). That COFF-file must have one public function ( compile_asm() ) which will be called by my extension. Input parameters for compile_asm() will be a pointer to some assembly code (dword) and the length of this code in bytes (dword). The output value will be a pointer to the compiled binary code (dword). Input and output parameters will be passed through the processor’s registers: Register Purpose
functions it will just return and nothing happens. The next function I’ve changed is exit_program(), which FASM calls when an error occurs, terminating the program. Let this function return 0, so if any errors occur during the compilation, compile_asm() will return zero: exit_program: xor add extension’s will be 3rd
eax, eax ; eax = 0 esp, 8 ; esp = esp+8, because return address dword in stack
ret
Fasm.asm is fasm’s main source file. It contains calls to the parser, preprocessor, assembler, formatter, includes for other files, EXE-sections, etc. All EXE-sections (imports, fixups) must be removed, as we don’t need ‘em anymore. And ‘format PE’, which indicates that the file will be compiled into Portable Executable format (.exe), must be changed to ‘format MS COFF’. FASM 1.39 uses 11 external WinApi functions, which must be declared in MS format: __imp__functioname@paramlength:returnlength
where functionname is the name of Windows API function, paramlength is the byte length of input parameter, and returnlength is the output type.
Input parameters EAX
Pointer to assembly code
EDX
Length of assembly code Output value
EAX
Pointer to compiled binary data
Declarations of Windows API functions: extrn __imp__ExitProcess@4:dword ExitProcess equ __imp__ExitProcess@4 extrn __imp__CreateFileA@28:dword CreateFile equ __imp__CreateFileA@28
The rest of this section describes changes I’ve made in FASM(ver. 1.39) source-files to recompile it as a COFF file.
extrn __imp__ReadFile@20:dword ReadFile equ __imp__ReadFile@20
system.inc and fasm.asm The system.inc source file consists of an interface to system functions like ReadFile, WriteFile, CreateFile etc. Since FASM will be used as a linked COFF-file, all screen-output functions (display_string, display_block, display_character and display_number ) should be removed:
extrn __imp__CloseHandle@4:dword CloseHandle equ __imp__CloseHandle@4
display_string: ret display_block: ret display_character: ret display_number: ret
extrn __imp__WriteFile@20:dword WriteFile equ __imp__WriteFile@20
extrn __imp__SetFilePointer@16:dword SetFilePointer equ __imp__SetFilePointer@16 extrn __imp__GetCommandLineA@0:dword GetCommandLine equ __imp__GetCommandLineA@0 extrn __imp__GetStdHandle@4:dword GetStdHandle equ __imp__GetStdHandle@4 extrn __imp__VirtualAlloc@16:dword VirtualAlloc equ __imp__VirtualAlloc@16 extrn __imp__GetTickCount@0:dword GetTickCount equ __imp__GetTickCount@0 extrn __imp__GlobalMemoryStatus@4:dword GlobalMemoryStatus equ __imp__GlobalMemoryStatus@4
So when some part of FASM code calls some of these August 2003 · PHP Architect · www.phparch.com
58
FEATURES
Embedding Assembler in PHP
I’ve also replaced FASM’s original entry point, ‘start:’, with: public _compile_asm lic
; let compile_asm() be pub-
_compile_asm:
Here’s a part of FASM’s code where it gets commandline parameters: call cmp je lea mov movzx add cmp je inc mov
get_params [params],0 information eax,[params+1] [input_file],eax ecx,byte [eax-1] eax,ecx byte [eax],0 information eax [output_file],eax
I’ve replaced this with: mov [str_src], eax
; str_src = pointer to assembly code (eax) mov [str_src_len], edx ; str_src_len = length of [str_src] (edx)
The last step is to forbid reading the source from the file and use data passed with input parameters instead of files. The source file with the “preprocess_file” function (file: preproce.inc) looks like this: preprocess_file: push push call jc mov xor call push xor xor call pop mov dec mov sub jc mov cmp jbe mov call call
[memory_end] edx open no_source_file al,2 edx,edx lseek eax al,al edx,edx lseek ecx edx,[memory_end] edx byte [edx],1Ah edx,ecx out_of_memory esi,edx edx,edi out_of_memory [memory_end],edx read close
... Which I’ve replaced with: “str_src” and “str_src_len” are variables I‘ve declared in fasm.asm: str_src dd ? str_src_len dd ?
assemble.inc,, preproce.inc,, formats.inc The ORG 0xXXXXXXXX - directive tells the assembler to move its location counter to 0xXXXXXXXX memory address. Before compilation is started, the source to be compiled must have the directive ORG 0xXXXXXXXX, where 0xXXXXXXXX is the entry point of the compiled binary code. Since we can’t know the entry point address before compiling is started, the org directive address (0xXXXXXXXX) needs to be changed during the compilation. It just needs to add “mov eax, [code_start]” after “call get_dword_value” within the ‘org_directive’ label (file: assemble.inc). The file ‘formats.inc’ contains code for formatting compiled code into FASM-supported output file formats(e.g. MZ, PE, COFF...). Because I only need to get simple binary code, all code after the “formatter:” label (file: formats.inc) may be replaced by: mov eax,[code_start] ;eax = code_start; ret
August 2003 · PHP Architect · www.phparch.com
preprocess_file: ; read, write, close commands are removed push [memory_end] push edx mov ecx, [str_src_len] mov edx,[memory_end] dec edx mov byte [edx],1Ah sub edx,ecx jc out_of_memory mov esi,edx cmp edx,edi jbe out_of_memory mov [memory_end],edx ; copying assembly source code from extension’s to fasm’s buffer: pushad ;saving registers mov esi, [str_src] mov edi, edx mov ecx, [str_src_len] mov [bytes_count], ecx rep movsb popad ; restoring registers ...
That’s it! FASM is now ready for recompilation! Compiling I’ve compiled these edited FASM sources with the original fasm.exe:
59
FEATURES
Embedding Assembler in PHP
> fasm.exe fasm.asm fasm.obj fasm.asm – input file fasm.obj – output file
access any other functions exported by shared libraries: HINSTANCE LoadLibrary( LPCTSTR lpLibFileName // address of filename of executable module );
and
Making the extension This simple extension consists of one source file – php_asm.cpp and provides only one function for PHP developers: int asm(string code)
It simply assembles and executes code. The return value is the value of the EAX processor’s register after the code has successfully completed. The basic extension code (C++) with comments can be seen in Listing 1 (included in this month’s package). After compiling and linking it with fasm.obj, I’ve got php_asm.dll. I’ve placed it in my PHP extension directory and I’ve also written a small script to test it (test1.php): ("php_asm.dll"); = asm(" org 0x00000000 use32 mov edx, dword [sux] mov ebx, 0x95511559 xor edx, ebx
push edx pop eax ret sux dd 0x87654321
; setting compilers location ; using 32bit code ; edx = 0x87654321 ; ebx = 0x95511559 ; edx = 0x87654321 xor 0x95511559 = 0x12345678 ; eax = edx ; returning
("%s %x","asmed:", $return );
Execution Output:
FARPROC GetProcAddress( HMODULE hModule, // handle to DLL module LPCSTR lpProcName // name of function );
The LoadLibrary() function maps the specified executable module (e.g. into the “user32.dll”) address space of the calling process and returns it’s handle. If the module is already loaded, the function just increments the reference count for the module and returns the module handle for that library. The GetProcAddress function returns the address of the specified exported dynamiclink library (DLL) function.[4]
“I've decided to add the ability to call Windows API functions and to work with variables from the PHP namespace.”
C++ example: /* getting handle of user32.dll */ h = LoadLibrary(“user32.dll”); /* getting MessageBoxA function’s address*/ f = GetProcAddress(h, “MessageBoxA”);
I need to provide to the compiled binary code at least these two functions for accessing any other parts of the API. I’ve created an array of DWORDs called “Func_AddressTable” (unsigned long Func_AddressTable[2];). Each element of this array is an address of a function:
It’s alive! So the extension works, and now I’ve decided to add 2 important features: the ability to call Windows API functions and the ability to work with variables from the PHP namespace. Accessing The Windows API Any Win32 program requires two API functions to
August 2003 · PHP Architect · www.phparch.com
60
FEATURES Func_AddressTable will be initialized within the extension’s module startup function ZEND_MINIT_FUNCTION(asm) (this function is called only once, upon module initialization[3]) : ZEND_MINIT_FUNCTION(asm){ HMODULE h;
Embedding Assembler in PHP windows message box:
h = GetModuleHandle(“kernel32.dll”); Func_AddressTable[0] = GetProcAddress(h, ”LoadLibraryA”); Func_AddressTable[1] = GetProcAddress(h, ”GetProcAddress”);
Func_AddressTable will be passed to the compiled code through the memory stack. So our __asm{} statement within ZEND_FUNCTION(asm) will be different:
ecx, [esp+4] ; ecx = addr_tbl dword [ecx] [load_lib ] ; load_lib = LoadLibrary () dword [ecx+4] [get_prc ] ; get_prc = GetProcAddress ()
push call
lib_u32 [load_lib ]
push push call
mbname eax [get_prc ]
xor
ecx, ecx
; eax = LoadLibrary (“user32 .dll”);
; eax = GetProcAddress (eax, “MessageBoxA” ); ; ecx = 0
__asm{ ; saving general purpose registers in stack mov eax, str2asm ; eax = pointer to string with assembly code mov edx, str2asm_len ; edx = length of str2asm mov ecx, f_call call ecx ; calling for compile_asm() function mov ebp, [esp+8] ; restoring ebp-register test eax, eax jz err ; if eax == 0 goto err push addr_tbl ; passing Func_AddressTable call eax ; calling for compiled binary code pop edi ; removing Func_AddressTable from stack
pushad
push push push push call ret
ecx mb_title mb_text ecx eax ; MessageBoxA (0,title, text,0)
load_lib dd 0 get_prc dd 0 lib_u32 db ‘user32 .dll’,0 mbname db ‘MessageBoxA’ ,0 mb_title db ‘hello world !’,0 mb_text db ‘The sky above the port was the color of television , tuned to a dead channel .’,0 “); ?>
err: mov ebp, [esp+8] ; restoring ebp-register mov f_res, eax ; saving eax (f_res = eax) popad ; restoring registers
Executing:
}
Where addr_tbl looks like this: void *addr_tbl; addr_tbl = Func_AddressTable;
When the code above executes “call eax” to run compiled code, the stack within the compiled code looks like this:
I’ve written a small script to test this feature – test2.php. The script gets the address of the WinAPI MessageBoxA function and displays text in a standard August 2003 · PHP Architect · www.phparch.com
Note: There’s a way to get addresses of the LoadLibrary and GetProcAddress functions without any APIs. But I thought that the way described above was more convenient. Moreover, I’ll also need Func_AddressTable to provide access to the script’s variables (see the next section of this article).
Accessing a script’s variables Another important feature is the ability of inline assembler to work with variables created within a PHP script. I’ve added 3 more functions to the Func_AddressTable (Table 1 on the next page). The set_var() function creates a new variable and assigns it a value. If the variable name passed to the
61
FEATURES
Embedding Assembler in PHP
set_var() function already exists, the function overwrites it. PHP variable types are described in the zend.h file from the PHP source package. (e.g. The type for string variables is 3). Currently, only LONG, BOOL and STRING types are supported.
unsigned long __stdcall set_var(char *name, unsigned long type, unsigned long data){ zval *var1; #ifdef ZTS /* TSRM */ void ***tsrm_ls; tsrm_ls = asm_tsrm_ls; #endif
Implementation of these functions:
/* making empty zval container */ MAKE_STD_ZVAL(var1);
unsigned long __stdcall get_var_type(char *name){ zval **var1; #ifdef ZTS /* TSRM */ void ***tsrm_ls; tsrm_ls = asm_tsrm_ls; #endif if(zend_hash_find(EG(active_symbol_table), name, strlen(name)+1, (void **) &var1)==SUCCESS){ /* finds variable in current active symbol table and returns it’s type */ return (*var1)->type; } /* returns 0xffffffff if variable name not found */ return 0xffffffff; }
unsigned long __stdcall get_var_data(char *name){ zval **var1; #ifdef ZTS /* TSRM */ void ***tsrm_ls; tsrm_ls = asm_tsrm_ls; #endif /* finding variable */ if(zend_hash_find(EG(active_symbol_table), name, strlen(name)+1, (void **) &var1)==SUCCESS){ /*returns variable value depending on it’s type */ switch((*var1)->type){ case IS_NULL: return 0; case IS_LONG || IS_BOOL: return (*var1)->value.lval; case IS_STRING: return (unsigned long)(*var1)->value.str.val; } } return 0; }
/* assigning it’s type */ var1->type = type; /* assigning it’s data depending on it’s type */ switch(type){ case IS_LONG || IS_BOOL: var1->value.lval = data; break; case IS_STRING: var1->value.str.len = strlen((char *)data); var1->value.str.val = estrdup((char *)data); break; } /* inserting var1 to current active symbol table */ ZEND_SET_SYMBOL(EG(active_symbol_table), name, var1); return 0; }
Changed Func_AddressTable initialization: ZEND_MINIT_FUNCTION(asm){ HMODULE h; h = GetModuleHandle(“kernel32.dll”); Func_AddressTable[0] = GetProcAddress(h,”LoadLibraryA”); Func_AddressTable[1] = GetProcAddress(h,”GetProcAddress”); (void*)Func_AddressTable[2] = get_var_type; (void*)Func_AddressTable[3] = get_var_data; (void*)Func_AddressTable[4] = set_var; return SUCCESS; }
I’ve changed test2.php such that the message text and message type are assigned using variables from the PHP namespace.
Table 1: Addtions to the Func_AddressTable
Function
Parameters
Return Value
unsigned long __stdcall name - Points to a null-terminated string Returns a number representing variable type get_var_type(char *name) containing the name of a PHP variable (see zend.h from PHP sources) Returns 0xffffffff if variable not found. unsigned long __stdcall name - Points to a null-terminated string Returns value of a variable if variable type is get_var_data(char *name) containing the name of a PHP variable long or bool. Returns pointer to string if variable is string. unsigned long __stdcall name - Points to a null-terminated string set_var(char *name, unsigned long containing the name of a PHP variable. type, unsigned long data) type - variable type (see zend.h) data - new value of the variable name if variable is long or bool. Pointer to null-terminated string if variable is string.
August 2003 · PHP Architect · www.phparch.com
Currently always returns zero.
62
FEATURES
test3.php:
ecx, [esp+4] dword [ecx] [load_lib ] dword [ecx+4] [get_prc ] dword [ecx+0xC] [get_var_data ] ; get_var_data = get_var_data ()
push call push push call mov
lib_u32 [load_lib ] mbname eax [get_prc ] [MessageBoxA ], eax ; MessageBoxA = MessageBoxA ()
push call push
mb_flags [get_var_data ] ; eax = get_var_data (“m_type” ); eax
push
mb_title
push call
mb_text [get_var_data ] ; eax = get_var_data (“m_text” ); eax dword 0 [MessageBoxA ] ;MessageBoxA (0, “hello...”, $m_text , $m_type)
push push call ret
load_lib dd 0 get_prc dd 0 get_var_d ata dd 0 MessageBoxA dd 0 lib_u32 db ‘user32 .dll’,0 mbname db ‘MessageBoxA’ ,0 mb_title db ‘hello world !’,0 mb_text db ‘m_text’ ,0 mb_flags db ‘m_type’ ,0
Embedding Assembler in PHP script creates a new thread within the current process via the CreateThread function, and returns from the inline assembler routine. The new thread continues to run even when control returns from the inline assembler statement to PHP. This thread creates the window and starts to listen for windows messages. It calls the click procedure (which sets the $is_clicked variable to zero) when WM_LBUTTONUP message is received. The script’s infinite loop checks $is_clicked. If $is_clicked is equal to 0 then the script sets $is_clicked to a non-zero value, increments the click count and prints a message. So every time you click the window, PHP will print “Click! (...)”. To see the output of this example, see Figures 1-3.
“The new thread continues to run even when control returns from the inline assembler statement to PHP.”
Security notes Actually there’s no security at all. Asmed code executes within PHP’s process, so it could damage PHP’s or even Apache’s process memory (if php compiled as apache Figure 1: example.php output
“); ?>
Executing:
Example The script named ‘example.php’ (included in this month’s package) creates a window and prints “Click! (i)” (i is the value of a click counter) to php’s output when the user releases the left mouse button while the cursor is in the client area of a window. (WM_LBUTTONUP Windows message [4]). After all of the requisite API addresses are loaded, the
August 2003 · PHP Architect · www.phparch.com
63
FEATURES module). Programmers could call interrupts, OS functions (e.g. ExitWindows) or even write or read hardware ports from a PHP script! It’s not very smart (actually, that’s not smart at all) to give public access to this extension on your server. In general it’s pretty lame to give access to some low level capabilities to other people on your server. And it’s much preferable to “extend” php with PHP extensions rather than inline assembler. ToDo This article just demonstrates one way to extend PHP, offering new capabilities. This is just an example of a non-standard use of PHP. I’m not currently considering supporting this project in future. But I still have some ideas to improve the extension if someone is interested:
Embedding Assembler in PHP • Ability to call a script's user functions from inline assembler • Rewrite the extension's __asm{} statement in ZEND_FUNCTION(asm) in C++ • Port to Linux • Support of all PHP variable data formats for inline assembler • Compilation error handling
About The Author
?>
Igor is a student of Moscow State Institute of Electronics and Mathematics: Networking faculty. You can reach him at [email protected].
Click HERE To Discuss This Article http://www.phparch.com/discuss/viewforum.php?f=42
Related Links http://webster.cs.ucr.edu/Page_asm/ArtofAssembly/0_ArtofAsm.html – The Art of Assembly Language http://fasm.sourceforge.net – FASM homepage http://www.zend.com – Zend homepage http://msdn.microsoft.com – Microsoft Developer Network http://osdev.neopages.net/tutorials/gccasmtut.php – A Brief Tutorial on GCC inline asm http://win32assembly.online.fr – Iczelion’s Win32 Assembly tutorials http://search.cpan.org/perldoc?Inline – Perl’s Inline module
Figure 2: example.php output Figure 3: example.php output
August 2003 · PHP Architect · www.phparch.com
64
By John W. Holmes
T I P S
&
T R I C K S
Tips & Tricks
Don’t Settle for the Defaults Does your host have a setting in its php.ini file that annoys you? Or maybe it breaks your programs? Well, for most of the settings, you don’t have to settle for what your host has given you. PHP happily provides you the ini_set() function so that you can adjust the settings within your scripts. Maybe you want your session to last a little longer than the life of the browser, or want to save them in another place, or just really need to have magic_quotes_runtime enabled. If so, ini_set() is for you. Want to fix that pesky register_globals problem (by turning them OFF, of course) or the magic_quotes_gpc setting? Well, ini_set() is not for you. While the function can be used to adjust a lot of the settings for PHP, it can’t change them all. Some are restricted so that they can only be changed within the php.ini file, others can be changed with an .htaccess file, and a few can be changed in your script. How do you know the difference? Table 1 shows an excerpt from the ini_set() manual page for some common settings. The first column is the name of the setting, the second is the default for the setting, and the third column tells you where the setting can be changed at.
August 2003 · PHP Architect · www.phparch.com
There are four options for this column:
PHP_INI_USER
Entry can be set in your scripts
PHP_INI_PERDIR
Entry can be set in php.ini, .htaccess, or httpd.conf
PHP_INI_SYSTEM
Entry can be set in php.ini or httpd.conf
PHP_INI_ALL
Entry can be set anywhere
You won’t actually see any settings that say as if it’s able to be set in your scripts then it can be set in any of the other locations, also. So looking at Table 1, we can set magic_quotes_runtime/sybase with ini_set() within our scripts, magic_quotes_gpc in an .htaccess or php.ini file, and safe_mode only within php.ini. When you’ve made the change you want, whether using ini_set() or an .htaccess file, you can view the output of the phpinfo() function to ensure your PHP_INI_USER,
65
TIPS & TRICKS change has taken place. The phpinfo() function will show you the Local Value of the setting (which should match what you tried to change it to) and the Master Value from the php.ini file or the PHP defaults. An .htaccess file is just a plain text file that is named just that, .htaccess. It’s only available if you’re using Apache as your web server, though. If you’re using an .htaccess file to change your settings, there are two ways you can do it. To change the value of a setting that’s boolean or ON/OFF, you would use the format: php_flag ON|OFF
To turn register_globals OFF, for example, you’d use:
“If the client will not accept a session cookie and you want to use sessions, you're forced to pass the session identifier along with every request.”
php_flag register_globals OFF
If the setting you’re changing is not boolean, but instead requires a value, then you’d use the format: php_value
To change the path that your sessions are saved to, for example, you’d use: php_value session.save_path “/path/to/dir”
If you set the value to none, it’ll clear a previously set value. Passing the SID So, if you haven’t used the first tip to enable session.user_trans_sid and you’re passing the value by hand, a DevShed.com user named jpenn offered up the solution in Listing 1. If the client will not accept a session cookie and you want to use sessions, you’re forced to pass the session identifier along with every
Listing 1 function append_url( $url ) { if ( isset( $_COOKIE[ session_name() ] ) ) { /*—————————————————————— Return Un-Modified —————————————————————— */ return( $url ); } else { if ( strstr( $url, '?' ) ) { $arg_sep = '&'; } else { $arg_sep = '?'; } /*—————————————————————— Return Appended —————————————————————— */ return( $url . $arg_sep . SID ); } }
Table 1 magic_quotes_gpc
“1”
PHP_INI_PERDIR|PHP_INI_SYSTEM
magic_quotes_runtime
“0”
PHP_INI_ALL
magic_quotes_sybase
“0”
PHP_INI_ALL
output_buffering
“0”
PHP_INI_PERDIR|PHP_INI_SYSTEM
NULL
PHP_INI_PERDIR|PHP_INI_SYSTEM
register_argc_argv
“1”
PHP_INI_PERDIR|PHP_INI_SYSTEM
register_globals
“0”
PHP_INI_PERDIR|PHP_INI_SYSTEM
safe_mode
“1”
PHP_INI_SYSTEM
output_handler
August 2003 · PHP Architect · www.phparch.com
66
TIPS & TRICKS request. This means every link or form on your page must pass the SID value. This function will test to see if a cookie equal to the session name exists. If it does, the original URL is returned, as SID does not need to be appended. It then checks to see if a query string is already present or not in the URL, then it adds either a “?” or “&” character and the SID. The function then returns the complete URL.
Listing 2
Session Fixation Fix I wrote in the January 2003 issue about PHP having a “permissive” session management system. The key to this claim was that PHP will take any value passed as PHPSESSID in the URL and use that as the session ID when you call session_start() in your script. A malicious user could get other users to click on a link and set the session ID for them. This would make session hijacking quite easy.
if(!isset($_SESSION['count'])) { $_SESSION['count'] = 0; }
“A malicious user could get other users to click on a link and set the session ID for them. This would make session hijacking quite easy.” As of PHP 4.3.2, there is now a session_regenerate_id() function that can help protect you against session fixation and session hijacking. Calling this function after session_start() will
session_start(); if(rand(1,10) == 5) { session_regenerate_id (); } setcookie(session_name(),session_id()); echo "Session ID: " . session_id();
$_SESSION['count']++; echo "
Count: {$_SESSION['count']}"; echo "
Go";
assign a new session ID and still preserve your data. This means you can call this at any point in your script. Calling it directly after someone verifies their login data will prevent session fixation attacks. Calling it periodically throughout your scripts, or as users pass into different areas of your application can help prevent session hijacking. There are some problems with this “solution” though. Only PHP 4.3.3 will actually reset the value of the session cookie when you call session_regenerate_id(). If you’re using cookies and not using PHP 4.3.3, then even though the session ID is changed, it’s reset back to the old value when the next request is made and the old value is given in the session cookie. The solution to this is to reset the cookie yourself after you generate a new ID. Listing 2 shows an example counter script that’ll randomly change the value of the session ID. Using setcookie() after generating a new session ID resets the session cookie to the new ID. If you are not using cookies, you can remove the setcookie() function call and notice how SID will be populated and the session data will persist even as the session ID changes. You can test this by disabling cookie use with ini_set(). Remember my first tip? ini_set(“session.use_cookies” ,0);
Dynamic Web Pages www.dynamicwebpages.de sex could not be better | dynamic web pages - german php.node
news . scripts . tutorials . downloads . books . installation hints
August 2003 · PHP Architect · www.phparch.com
Remember Where Your User Was Maybe it’s happened to you. You spend some time browsing through a site looking for something. When you happen to find it, you take your time reading it. However, when you click on the next link, your session has timed out and you’ve got to log in again. The problem with this is that the login process dumps you back out to the main page, though, and you’ve got to find your file again. Sure, bookmarking the page would be one option, but as programmers, we should feel for our users and make things easy for them.
67
TIPS & TRICKS So what’s the answer? Well, it depends on how you’re controlling your authentication process, but in general terms, you want to save the URL the user requested somewhere. When the user properly revalidates their login, then you retrieve that URL and redirect them to there, instead of your main page. Each of the parts to make up the URL that was requested can be found in $_SERVER. Depending upon your web server, you may need to combine a few variables or you may find one variable that contains the entire URL. A simple print_r($_SERVER) will show you all of the contents and you can pick from there. This idea could be expanded some. As it is right now, it’ll only cover GET requests, or URLs with a query string. If the timeout occurs when a form is POSTed, you lost that POST information. However, upon realizing the session has timed out, you could check $_POST for values and save that in the session also. Then simply restore it on the page the user is redirected back to and their data will be saved. This will make the user that takes 2 hours to type up his post to your bulletin board very happy. The Difference Between “Works” and “Right” There was a recent, well, "discussion" on the PHP mailing list where people were discussing issues with header(). The manual states that HTTP/1.1 requires a
August 2003 · PHP Architect · www.phparch.com
full URI when sending a Location: header, such as header(‘Location: http://www.domain.com’). At this point, some people piped up and stated that they have been using relative URIs and it’s working just fine. Well, that’s the difference between “working” and “right”. Yes, relative URIs might work with some clients, but not all of them. If it’s done the right way, it’s going to work regardless, which is pretty much what we want in this business. The other tip that was offered when redirecting with a Location: header is to always put a exit() after the header() call. The PHP code doesn’t stop processing when the Location header is sent. Depending upon the code you have after the redirection, it could cause the redirection to fail, even though the code might work in some cases. Adding in the exit() makes sure nothing else is executed and the client processes the new Location: header. That, again, is the difference between “works” and “right”.
About The Author
?>
John Holmes is a Captain in the U.S. Army and a freelance PHP and MySQL programmer. He has been programming in PHP for over 4 years and loves every minute of it. He is currently serving at Ft. Gordon, Georgia as a Company Commander with his wife and two sons.
php|a
68
Zeev, the Grand Loco Back in June, John Lim (of ADODB fame), speaking about the JSR 223 wrote: “I was hooked the first time i took a drag of PHP. All my complicated C shit became clear after one puff of PHP. It was so addictive i couldn’t stop. I had to prowl the PHP CVS, meet drug dealers like Sterling and Andrei, and even hang out with the grand loco himself, the Zeev.” —http://php.weblogs.com/ Interestingly enough, just a few days earlier, Zeev had uploaded this picture to his personal gallery (http://www.suraski.net/gallery). Looks like he’s ready for the PHP cruise... Are you?
B I T S
cipal applied to programming, i.e. the variable does not really take on a value until you look at it” — Jason Sweat, regular php|architect author See the full thread at: http://www.phparch.com/discuss/viewtopic.php?t=163 “A consultant that judges code based on its appearances reminds me of what Brendan Behan once said of literary critics: ‘A critic is like a eunuch at on orgy. He knows how it’s done. He’s seen it done every day. But he’s unable to do it himself’.” — Marco Tabini, publisher of php|a See the full blog entry at: http://blogs.phparch.com/mt/archives/000048.html “What’s a polar bear?” “A rectangular bear after a coordinate transform.” — Bill White (appeared in a newsgroup posting by Andrei Zmievski, PHP internals hacker and PHP-GTK developer)
&
P I E C E S
Bits & Pieces
Security by Obscurity Recently on comp.lang.php there was a rather heated discussion on security by obscurity. Everyone knows (or will learn) that security by obscurity is not really security at all, but Bill S offered an interesting analogy on the subject. His original question to the list was about how to prevent the version of PHP from being detected, for which he offered the following reason: Taken by Anya
Interesting Quotes “The reasonable man adapt himself to the world; the unreasonable one persists in trying to adapt the world to himself. Therefore all progress depends on the unreasonable man.” — George Bernard Shaw
“As for why I want to do this, it is a paranoid attempt to gain an admittedly small measure of security. If all the pages are served with htm/html extensions, one would have to resort to other means to ascertain what back end scripting language, if any, is in use. I imagine there could be some future php exploits, so I would prefer to make the task a little more difficult.”
“...with straight C (and messy pointer problems) you can add a printf to display the value of a variable, and have the code work, comment out the printf and the code fails. I consider this Heisenberg’s uncertainty prin-
As you can imagine, this raised some hackles. His further explanation offers what I found to be an interesting analogy, although I think it fails to hold a great deal of water in this particular situation.
August 2003 · PHP Architect · www.phparch.com
69
Bits & Pieces
“If I took a lot of time to plug almost all the major holes in the cheese, I would still cover the while thing with a napkin since I can’t be 100% certain that I have not missed a small hole or two, or perhaps a hole could materialize later, and then the napkin will serve a useful purpose in obscuring the remaining holes. Now, a lot more effort and highly concentrated pokes are required to find a hole, and hopefully the effort required to find those holes will discourage most, as they move on to easier targets. If a hole becomes known, by all means do not rely on the napkin—fill it! If you become complacent by depending on the napkin to stop a poke, and you quit checking for new holes, then it is a matter of time before the worst happens. As the number of holes inevitably grows, the probability of a breach grows, so the effectiveness of the napkin diminishes over time. As the size and number of holes diminishes, but is never zero, the probabilistic effectiveness of the napkin grows against random attackers. Of course, if someone happens to know your cheese very well, the napkin will be useless against that person since he knows exactly where to poke, even though it may have served a useful purpose against a number of other assailants that didn’t know where to look. The napkin should not be judged as ineffective just because of this breach since it prevented a number of other otherwise successful attacks from people who were merely probing for holes.” “So assuming a sound overall security policy is in place, and we have prevented a number of otherwise successful attacks, if I can furthermore prevent at least one otherwise successful attack by obscuring information about my system in some way (OS, directories, servers) at minimal cost and without breaking normal client interactions, I will do it in addition to the obviously needed robust measures.” A later post by Brandon Blackmoor offered the following fatal flaw: “The “napkin” (the illusion of security) is counterproductive because it *delays* the discovery and removal of security holes, and because it creates a false sense of security. Eschew ‘napkins’.” An interesting discussion. August 2003 · PHP Architect · www.phparch.com
Errata:
There was a small error in last month’s Geeklog article, found in a comment thread on the Geeklog site (http://www.geeklog.net/article.php?story=2003071 1214739561#comments). Dtrumbower, on July 14 2003, mentioned that the link in installation step 8 should be http://yourgeeklogsite/admin/install/install.php, not http://yourgeeklogsite/admin/install/check.php.
Monster Quiz This is a skill-testing question taken from a recent job posting (related to PHP) on http://www.monster.ca. See if you’d get the job. ** How to apply ** • The subject of the email must be the result of the Subject() function as defined below. • Attach your résumé • Send the email to [email protected]
Note: This is not in any particular language and is meant to be doable with paper & pencil.
Define Compute_DA(x) { temp = (x + 1) / 4; if (temp >= 1) then return x * Compute_DA(temp+1); else return x; } Define Subject() { LookingFor = “Job”; Job = “Good”; LaserRegistration = “Ultimate”; Programming = Get Pointer(“Job”); Answer = Programming->; Quote = Char(34); Test = “Job=” + Quote + LaserRegistration + “Answer” + Quote; Evaluate(Test); Solution = Job + “=” + Compute_DA(7); return Solution; }
• The answer (at least the answer I got) is UltimateAnswer=22
“Frankly, I don’t see the harm in making things less obvious as long as one does not rely primarily on obscurity as a security policy—that would be akin to covering a block of swiss cheese with a napkin and saying it is impenetrable. It would not require too many pokes to find a gaping hole in the cheese, since the napkin is clearly not offering strong protection, and no one in their right mind should think so.”
php|a
70
Expect More From php|a By Marco Tabini
e x i t ( 0 ) ;
T
raditionally, the summer months are a period of rest and relaxation for the publishing industry, when journalists and editorial staff alike take a break from the hectic schedule that getting a magazine on the newsstands (even the virtual kind) every month dictates. In Italy, most specialized magazines don't even publish an issue in August—and skip directly over to September, when they start gearing up for the traditional rush that the holiday season brings. For some reason, however, we at php|a have never been as busy as we have over the past few months— and the trend will likely continue for a long time to come. Just last month, we announced that in September we'll start publishing the print edition of our magazine—and, believe me, that is no small task. Thanks in part to the fact that some of us have had a long experience with the print world, the transition has been going very smoothly, but it remains a long process, and fraught with peril at every corner. We take it on gladly, of course, knowing that we'll satisfy the needs of many new readers—and, hopefully, of some existing ones. This month, we're finally announcing the winners of our Grant Program. It took several weeks to decide which were the best choices from the hundreds of proposals that we received. I'm happy to report that both our winners have the great potential of taking PHP into new worlds—the medical management arena, known for its strict requirements for accuracy and reliability, and network management, where interaction with a multitude of different systems is paramount. Our new website, which we also launched last month (and was cause of many a discussion on the usefulness of code validation), provides us with a much more stable platform for promoting our products—and, much more importantly, for interacting even better with you. We've recently stepped up our news reporting efforts (and, dare I say, Eddie Peloke, our online news editor, is doing an excellent job). We've even started selling PHP-related products through our online store (http://www.phparch.com/shop_phpa.php), with the long-term goal of creating a unique shopping experience designed exclusively for PHP users. In the months to come, expect us to add more and more products to our store at the best prices around, and because our store is dedicated only to PHP, you can also expect the lowest noise-to-signal ratio. If that wasn't enough, this month we're announcing yet another initiative, one that is, in my opinion, both fun and original. PHP conferences have been around for a long time, and some of them have gotten really good. However, being that none of us really gets any vacation time, we're trying to take one step further, and hold a PHP conference onboard a beautiful and luxurious cruise ship sailing to the Caribbeans. Over the course of five days, we'll have plenty of time to chill out—and, given the amount of time that we spend indoors, get lots of sunburn. Just because we're going on a cruise, however, doesn't mean that we're not serious about learning PHP: we've scheduled over 50 hours of talks (in 26 two-hour sessions) from some of the best PHP minds in the world. And, with early-bird prices starting at $799 (inclusive of all conference fees, full accommodation and all meals aboard the ship), and two chances to get your pass for free if you sign up before October 31st, we think it's a pretty good deal, too. You can find out more about this event by taking your browser to http://www.phparch.com/cruise. We'd love to see you there! As you see, we're always at work to bring new and exciting initiatives, and trying to improve the ones we've already started. More is coming over the next few months, as our family expands into new territory—but, at the base, there remains our commitment to remain open and accessible about what we do. We'll always be just one e-mail (at [email protected]) or forum post (at http://www.phparch.com/discuss) away. Just don't talk to me about W3C coding standards, and we can all be friends!
August 2003 · PHP Architect · www.phparch.com
71