0% found this document useful (0 votes)
1K views503 pages

Upgrading PHP 5

This new book is targeted toward PHP developers who are already familiar with PHP 4. Upgrading to PHP 5 offers a concise appraisal of the differences between PHP 4 and PHP 5, a detailed look at what’s new in this latest version, and explanation of how this changes affect you. The book also covers more advanced features and provides hands-on expreriences through short, sample programs included throughout.

Uploaded by

Mark Moran
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
1K views503 pages

Upgrading PHP 5

This new book is targeted toward PHP developers who are already familiar with PHP 4. Upgrading to PHP 5 offers a concise appraisal of the differences between PHP 4 and PHP 5, a detailed look at what’s new in this latest version, and explanation of how this changes affect you. The book also covers more advanced features and provides hands-on expreriences through short, sample programs included throughout.

Uploaded by

Mark Moran
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 503

< Day Day Up >

• Table of Contents
• Index
• Reviews
• Examples
• Reader Reviews
• Errata
• Academic
• Why PHP 5 Rocks!
Upgrading to PHP 5

By Adam Trachtenberg

Publisher: O'Reilly
Pub Date: July 2004
ISBN: 0-596-00636-5
Pages: 304

This new book is targeted toward PHP developers who are already familiar with PHP 4.Upgrading to
PHP 5 offers a concise appraisal of the differences between PHP 4 and PHP 5, a detailed look at
what's new in this latest version, and an explanation of how these changes affect you. The book also
covers more advanced features and provides hands-on experienced through short, sample programs
included throughout.

< Day Day Up >


< Day Day Up >

• Table of Contents
• Index
• Reviews
• Examples
• Reader Reviews
• Errata
• Academic
• Why PHP 5 Rocks!
Upgrading to PHP 5

By Adam Trachtenberg

Publisher: O'Reilly
Pub Date: July 2004
ISBN: 0-596-00636-5
Pages: 304

Copyright
Preface
Who This Book Is For
Who This Book Is Not For
What's in This Book
Other Resources
Conventions Used in This Book
Using Code Examples
We'd Like to Hear from You
Acknowledgements
Chapter 1. Introduction
Section 1.1. Why PHP 5?
Section 1.2. What's New in PHP 5?
Section 1.3. Installing and Configuring PHP 5
Chapter 2. Object-Oriented Programming
Section 2.1. What Is Object-Oriented Programming?
Section 2.2. Memory Management
Section 2.3. Basic Classes
Section 2.4. Class Intermediates
Section 2.5. Inheritance
Section 2.6. Magical Methods
Chapter 3. MySQL
Section 3.1. Installing and Configuring
Section 3.2. Procedural Interface
Section 3.3. Before and After: Connecting to the Database Server
Section 3.4. Object-Oriented Interface
Section 3.5. Before and After: Querying and Retrieving Data with Prepared Statements
Section 3.6. Before and After: Subselects
Section 3.7. Transactions
Section 3.8. Before and After: Making Multiple Queries
Section 3.9. Securing Connections with SSL
Section 3.10. Porting Code and Migrating Databases
Chapter 4. SQLite
Section 4.1. SQLite Basics
Section 4.2. Alternate SQLite Result Types
Section 4.3. Object-Oriented Interface
Section 4.4. Indexes, Error Handling, and In-Memory Tables
Section 4.5. Transactions
Section 4.6. User-Defined Functions
Chapter 5. XML
Section 5.1. XML Extensions in PHP 5
Section 5.2. Installing XML and XSLT Support
Section 5.3. DOM
Section 5.4. SimpleXML
Section 5.5. Converting Between SimpleXML and DOM Objects
Section 5.6. Before and After: Reading XML into a Tree
Section 5.7. Before and After: Searching XML with XPath
Section 5.8. Reading XML as Events with SAX
Section 5.9. Before and After: Creating New XML Documents
Section 5.10. Before and After: Transforming XML with XSLT
Section 5.11. Validating Against a Schema
Chapter 6. Iterators and SPL
Section 6.1. Before and After: Using Iterators
Section 6.2. Implementing the Iterator Interface
Section 6.3. MySQL Query Iterator
Section 6.4. Chaining Iterators
Section 6.5. SimpleXML Iterator
Section 6.6. Before and After: Recursive Directory Iteration
Section 6.7. Implementing the RecursiveIterator Interface
Section 6.8. Array and Object Property Iteration
Section 6.9. Redefining Class Iteration
Section 6.10. Iterator and SPL Classes and Interfaces
Chapter 7. Error Handling and Debugging
Section 7.1. Before and After: Handling Errors
Section 7.2. The Benefits of Exceptions
Section 7.3. System Exceptions
Section 7.4. The Exception Class
Section 7.5. User Exceptions
Section 7.6. Setting a Custom Exception Handler
Section 7.7. Processing Errors with a Custom Handler
Section 7.8. Debugging Functions
Chapter 8. Streams, Wrappers, and Filters
Section 8.1. Using the Streams API
Section 8.2. Wrapper Overview
Section 8.3. Wrapper Details
Section 8.4. Creating Wrappers
Section 8.5. Filtering Streams
Section 8.6. Creating Filters
Chapter 9. Other Extensions
Section 9.1. SOAP
Section 9.2. Tidy
Section 9.3. Reflection
Chapter 10. PHP 5 in Action
Section 10.1. Defining Your Database Schema
Section 10.2. The Person Class
Section 10.3. The addressBook Class
Section 10.4. The Template Class
Section 10.5. Assembling the Application
Section 10.6. Wrap-Up and Future Directions
Appendix A. Introduction to XML
Section A.1. Comparing HTML and XML
Section A.2. Well-Formed XML
Section A.3. Schemas
Section A.4. Transformations
Section A.5. XML Namespaces
Section A.6. XPath
Appendix B. Additional New Features and Minor Changes
Section B.1. Passing Optional Parameters by Reference
Section B.2. New E_STRICT Error Setting
Section B.3. Treating Strings as Arrays Causes Errors
Section B.4. CLI Now Allows Individual Line Processing
Section B.5. CLI Always Provides argv and argc
Section B.6. Oracle (oci8) Extension Functions Renamed
Section B.7. New Configuration Directives
Section B.8. Updated COM Extension
Section B.9. Apache 2 Correctly Sets PATH_TRANSLATED
Section B.10. strrpos( ) Uses the Entire Needle
Section B.11. Windows 95 Support Dropped
Section B.12. old_function Eliminated
Appendix C. Installing PHP 5 Alongside PHP 4
Section C.1. General PHP 5 Configuration
Section C.2. Module and CGI
Section C.3. Windows
Colophon
Index

< Day Day Up >


< Day Day Up >

Copyright © 2004 O'Reilly Media, Inc.

Printed in the United States of America.

Published by O'Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472.

O'Reilly books may be purchased for educational, business, or sales promotional use. Online editions
are also available for most titles (http://safari.oreilly.com). For more information, contact our
corporate/institutional sales department: (800) 998-9938 or [email protected].

Nutshell Handbook, the Nutshell Handbook logo, and the O'Reilly logo are registered trademarks of
O'Reilly Media, Inc. Upgrading to PHP 5, the image of Galapagos tortoises, and related trade dress
are trademarks of O'Reilly Media, Inc.

Many of the designations used by manufacturers and sellers to distinguish their products are claimed
as trademarks. Where those designations appear in this book, and O'Reilly Media, Inc. was aware of
a trademark claim, the designations have been printed in caps or initial caps.

While every precaution has been taken in the preparation of this book, the publisher and author
assume no responsibility for errors or omissions, or for damages resulting from the use of the
information contained herein.

< Day Day Up >


< Day Day Up >

Preface
PHP is a powerful yet easy-to-use tool for creating dynamic web content. The perfect language for
projects great and small, PHP is used by Yahoo!, millions of personal home pages, and everything in-
between. Best of all, PHP is open source and runs on Unix, Windows, and Mac OS X.

The latest version, PHP 5, contains robust support for object-oriented programming, an integrated
suite of XML tools, an all-new MySQL extension, web services with SOAP and REST, and hundreds of
other improvements.

There are so many updates and new features, it's difficult to get a handle on all the changes.
Documentation is scarce, scattered, and rarely in-depth. This book steadily guides you from the world
of PHP 4 to PHP 5, covering what's completely new, what's received an overhaul, and what's just
slightly different enough to slip you up.

< Day Day Up >


< Day Day Up >

Who This Book Is For


This is the PHP 5 book for PHP 4 programmers. If you're currently using PHP and want to learn what's
new in PHP 5, you should buy this book. Unlike other PHP 5 books,Upgrading to PHP 5 assumes
you're familiar with PHP, so it doesn't waste your time discussing how to process forms and query
databases.

Instead, Upgrading to PHP 5 shows you a complete collection of detailed examples covering all the
latest and greatest features of PHP 5. Object-oriented programming? Check. MySQL? Check. XML?
Check. If it's part of PHP 5, it's part of this book.

Besides showing off PHP 5, this book also eases your migration from PHP 4 to PHP 5. As a veteran
PHP programmer, you have pages and pages of existing PHP code. Not all of it will work perfectly
under PHP 5. That's why Upgrading to PHP 5 provides you with detailed side-by-side comparisons of
PHP 4 and PHP 5 solutions. This lets you clearly see the exact changes you need to make to your
current code.

In addition to covering what's new and how it's new, this book tells youwhy it's new. PHP 5 isn't
change for the sake of change. Upgrading to PHP 5 explains the logic behind the changes and shows
how you can take advantage of them to the benefit of your code.

Finally, it provides an address book program. This concrete example combines the new features of
PHP 5, demonstrating their usefulness in the context of a complete application. The address book
shows you exactly how PHP 5 makes it faster and easier to develop more powerful and flexible web
applications.

< Day Day Up >


< Day Day Up >

Who This Book Is Not For


This book is not for people new to PHP. If you're looking to learn the language, check out these three
excellent alternatives:

1. Learning PHP 5, by David Sklar. This book provides a gentle introduction to PHP for web
designers and other nontechnical people familiar with HTML and the Web, but not with
programming and databases.

2. Programming PHP, by Rasmus Lerdorf and Kevin Tatroe. Co-authored by the creator of PHP,
Programming PHP is aimed at anyone looking to learn PHP. An exhaustive reference, this book
covers all aspects of PHP, from basic concepts to advanced techniques.

3. Web Database Applications with PHP and MySQL, by Hugh E.Williams and David Lane. If you're
comfortable programming but have only just started using SQL, this book teaches you how to
create an entire database-backed web site. Web Database Applications with PHP and MySQL
works you through the entire process, including design, coding, and implementation.

These books are all published by O'Reilly Media, Inc.

< Day Day Up >


< Day Day Up >

What's in This Book


This book is divided into 10 chapters and 3 appendixes. The material in the first half of this book
covers vital components of everyday web programming: object-oriented programming, MySQL,
SQLite, and XML. It's hard to use PHP 5 without them. Specifically:

Chapter 1, Introductionexplains the background of PHP 5 and its new extensions.

Chapter 2, Object-Oriented Programming, covers object-oriented programming under PHP 5.

Chapter 3, MySQL, examines MySQLi, a new MySQL database extension. It also details SQL
features of MySQL 4.0 and 4.1.

Chapter 4, SQLite, talks about SQLite, an embedded database library.

Chapter 5, XML, teaches everything XML: DOM, XSLT, SimpleXML, SAX, and XPath.

The second half of the book discusses more specialized PHP 5 features. Iterators, exceptions,
streams, SOAP, and other extensions are all extremely useful for solving specific tasks. The more you
program in PHP 5, the more you'll find yourself integrating these features into your code.

Chapter 6, Iterators and SPL, shows how iterators and SPL let you loop through directories,
database queries, and XML documents.

Chapter 7, Error Handling and Debugging, helps you handle errors with exception handling.

Chapter 8, Streams, Wrappers, and Filters, introduces streams, wrappers, and filters.

Chapter 9, Other Extensions, discusses the SOAP and Tidy extensions and the Reflection
classes.

Chapter 10, PHP 5 in Action, puts the whole book together with a sample PHP 5 application.

The three appendixes complement the rest of the book:

Appendix A, Introduction to XML, provides an introduction to XML and Namespaces.

Appendix B, Additional New Features and Minor Changes, documents the changes in PHP 5 not
covered elsewhere in the book.

Appendix C, Installing PHP 5 Alongside PHP 4, details how to integrate PHP 5 into your existing
web server and PHP 4 setup.

< Day Day Up >


< Day Day Up >

Other Resources
These web sites and books are excellent companions to Upgrading to PHP 5.

Web Sites
There is a tremendous amount of PHP reference material online. With everything from the annotated
PHP manual to sites with periodic articles and tutorials, a fast Internet connection rivals a large
bookshelf in PHP documentary usefulness.

The Annotated PHP Manual (http://www.php.net/manual/)

The official PHP Manual contains thousands of pages covering all aspects of PHP. It's an
invaluable resource for looking up functions.

PHP mailing lists (http://www.php.net/mailing-lists.php)

Discuss PHP on the PHP mailing lists. Don't be shy, there's a list for every topic: programming,
databases, and even Windows. A mailing list archive lives athttp://news.php.net/.

PHP Presentation archive (http://talks.php.net/)

A great way to keep up-to-date on all the latest PHP developments, this archive contains
conference presentation slides.

PEAR (http://pear.php.net/)

Don't reimplement the wheel, download it from PEAR. PEAR-the PHP Extension and Application
Repository-contains PHP classes that simplify forms processing, provide a database
abstraction layer, generate class documentation, and solve hundreds of other tasks.

PECL (http://pecl.php.net/)

PECL is PEAR's sister. PECL-the PHP Extension Community Library-is a collection of PHP
extensions written in C. They're just like the bundled PHP extensions, except they're targeted
at a specialized audience. PECL contains may useful extensions, including a PHP cache and
optimizer, extensions to let you talk to Perl and Python from PHP, and an XML pull parser.

PHP DevCenter (http://www.onlamp.com/php/)

A large collection of PHP articles and tutorials freely available on the web.

PHPCommunity.org (http://www.phpcommunity.org/)

A gathering place where members of the PHP community can hang out and meet other PHP
programmers.

Books
These books are all helpful problem-solving guides and references. Most of the books in the list are
web-specific, and the top six books are my favorite PHP and MySQL texts.

1. PHP Cookbook,[1] by David Sklar and Adam Trachtenberg (O'Reilly, 2003).

2. Essential PHP Tools: Modules, Extensions, and Accelerators, by David Sklar (Apress, 2004).

3. Advanced PHP Programming, by George Schlossnagle (SAMS, 2004).

4. MySQL Reference Manual, by Michael "Monty" Widenius, David Axmark, and MySQL AB
(O'Reilly, 2002); also available at http://dev.mysql.com/doc/.

5. MySQL Cookbook, by Paul DuBois (O'Reilly, 2003).

6. High Performance MySQL, by Jeremy D. Zawodny and Derek J. Balling (O'Reilly, 2004).

7. XML in a Nutshell, Second Edition, by Elliotte Rusty Harold and W. Scott Means (O'Reilly, 2002).

8. HTTP Developer's Handbook, by Chris Shiflett (SAMS, 2003).

9. Web Security, Privacy & Commerce, Second Edition, by Simson Garfinkel and Gene Spafford
(O'Reilly, 2001).

10. Mastering Regular Expressions, Second Edition, by Jeffrey E. F. Friedl (O'Reilly, 2002).

< Day Day Up >


< Day Day Up >

Conventions Used in This Book


The following programming and typesetting conventions are used in this book.

Programming Conventions
The <?php and ?> opening and closing markers that begin and end a PHP program are generally
omitted from examples in this book, except in examples where the body of the code includes an
opening or closing marker.

The examples in this book were written to run under PHP 5 on both Unix and Windows, except where
noted in the text.

At the time this book went to press, the latest available version of PHP 5 was Release Candidate 3
(RC3). Also, MySQL 4.1 was in beta. It is possible that some changes may occur between RC3 and
the final release. Please check the online errata at
http://www.oreilly.com/catalog/upgradephp5/errata/ for any last-minute updates.

Typesetting Conventions
The following typographical conventions are used in this book:

Italic

Indicates new terms, URLs, email addresses, filenames, file extensions, pathnames, and
directories.

Constant width

Indicates commands, options, switches, variables, attributes, keys, functions, types, classes,
namespaces, methods, modules, properties, parameters, values, objects, events, event
handlers, XML tags, HTML tags, macros, the contents of files, or the output from commands.

Constant Width Bold

Indicates the output from code examples.


Constant width italic

Shows text that should be replaced with user-supplied values.

This icon signifies a tip, suggestion, or general note.

This icon indicates a warning or caution.

< Day Day Up >


< Day Day Up >

Using Code Examples


This book is here to help you get your job done. In general, you may use the code in this book in
your programs and documentation. You do not need to contact us for permission unless you're
reproducing a significant portion of the code. For example, writing a program that uses several
chunks of code from this book does not require permission. Selling or distributing a CD-ROM of
examples from O'Reilly books does require permission. Answering a question by citing this book and
quoting example code does not require permission. Incorporating a significant amount of example
code from this book into your product's documentation does require permission.

We appreciate, but do not require, attribution. An attribution usually includes the title, author,
publisher, and ISBN. For example: "Upgrading to PHP 5, by Adam Trachtenberg. Copyright 2004
O'Reilly Media, Inc., 0-596-00636-5."

If you feel your use of code examples falls outside fair use or the permission given here, feel free to
contact us at [email protected].

< Day Day Up >


< Day Day Up >

We'd Like to Hear from You


Please address comments and questions concerning this book to the publisher:

O'Reilly Media, Inc.


1005 Gravenstein Highway North
Sebastopol, CA 95472
(800) 998-9938 (in the United States or Canada)
(707) 829-0515 (international or local)
(707) 829-0104 (fax)

We have a web page for this book, where we list errata, examples, and any additional information.
You can access this page at:

http://www.oreilly.com/catalog/upgradephp5/

To comment or ask technical questions about this book, send email to:

[email protected]

For more information about our books, conferences, Resource Centers, and the O'Reilly Network, see
our web site at:

http://www.oreilly.com/

< Day Day Up >


< Day Day Up >

Acknowledgements
This book would not exist without the assistance of many, many, many people. They have my sincere
gratitude and thanks.

My largest debt belongs to the entire PHP community. Upgrading to PHP 5 could not exist without
your efforts. I want to specifically thank these people for their special assistance, whether they knew
they were giving it or not: Marcus Boerger, Zak Greant, Andi Gutmans, Sterling Hughes, Derick
Rethans, Rob Richards, Georg Richter, George Schlossnagle, Christian Stocker, and Hans Zaunere.

Thanks also to the members of NYPHP (http://nyphp.org/) for sharing problems, ideas, and solutions.
I'm lucky to have the world's best PHP user group in my hometown.

Portions of Chapter 7 were inspired in part by Gunjan Doshi's Best Practices for Exception Handling
article on ONJava.com (http://www.onjava.com/pub/a/onjava/2003/11/19/exceptions.html). I offer
him my thanks.

Bret Martin and Rik Faith provided hosting. Because of them, the technical review process was
smooth and easy.

Two people diligently reviewed Upgrading to PHP 5. Their comments and suggestions immensely
improved all aspects of the book. Chris Shiflett and David Sklar are the two best technical reviewers
an author could have. I am proud to call them my friends.

I want to thank all the associates of O'Reilly Media, Inc. for their help. I've met many O'Reilly
employees over the years, and there's not a bozo in the bunch.

These people, both directly and indirectly, contributed to the words, images, and pages you hold in
your hands:

Genevieve d'Entremont prepared this book for production and repaired my prose.

chromatic published my PHP articles on ONLamp.com.

A cornered Andy Oram and Bruce Epstein graciously provided editorial guidance and support
one late night at FOO Camp.

Rob Romano transformed my crayon sketches into works of art.

Betsy Waliszewski got the word out early and often.

However, two people contributed more messages to my inbox than the others combined: Tatiana
Diaz and Nat Torkington.

I have no idea how Tatiana Diaz put up with my all-too-often claims of "Oh sure, I can make that
deadline," without reaching through the Internet and strangling me. Without her gentle prodding,
Upgrading to PHP 5 wouldn't be available until PHP 6.
Nat Torkington is a great editor. He gave me a push when I got stuck and helped me reframe
chapters when they became misaligned. He even lets me make Perl 6 jokes at his expense. I pay him
the greatest compliment I can: this book would be far less helpful without him.

Thanks to my family and friends for their support and encouragement.

Extra special thanks go to Elizabeth Hondl, my beautiful princess and talking frog.

< Day Day Up >


< Day Day Up >

Chapter 1. Introduction
PHP 4 is a wildly popular web programming language. Web servers on over 15,000,000 domains
support PHP. PHP is the most popular Apache module by almost a 2-to-1 margin. But if PHP's so
great, why do we need PHP 5?

Well, PHP 4 isn't perfect. While it makes developing web applications quick and easy, it's occasionally
weighted down by its legacy baggage. This makes tackling some problems unnecessarily difficult.

In particular, PHP's version of object-oriented programming (OOP) lacks many features, the MySQL
extension doesn't support the new MySQL 4.1 client protocol, and XML support is a hodgepodge.

Fortunately, PHP 5 improves on PHP 4 in three major areas:

Object-oriented programming

MySQL

XML

These items have all been completely rewritten, turning them from limitations into star attractions.
While these changes alone warrant a new version of PHP, PHP 5 also provides:

SQLite for an embedded database

Iterators

Error handling using exceptions

Streams

SOAP

Some of these features, such as iterators and exceptions, are available only due to fundamental
changes in PHP's core. Others, such as streams and SQLite, are PHP 4.3 features that have matured
into prime-time use in PHP 5.

This book shows you how to take advantage of these new features in your applications. Additionally,
it places a special emphasis on not just telling you what's new, but showing you how and why it's
new.

Whenever possible, there's a direct comparison between the PHP 4 method of solving a task and the
PHP 5 solution. The PHP 5 way is frequently shorter, more elegant, and provides you with greater
flexibility. The before-and-after examples demonstrate in clear code what's better about PHP 5 and
provide you with concrete examples to ease the transition from PHP 4 to PHP 5.
This chapter serves as a dual introduction to both PHP 5 and Upgrading to PHP 5. It begins with a
brief history of PHP that provides the motivation behind PHP 5. The next section provides a short
description of all the major new features of PHP 5 and, at the same time, explains how the book is
organized. This chapter ends with a discussion of how to install and configure PHP 5.

< Day Day Up >


< Day Day Up >

1.1 Why PHP 5?


To understand why PHP 5 came to exist, it's necessary to quickly review the evolution of PHP as a
language.

When Rasmus Lerdorf created PHP back in 1995, it wasn't even called PHP-his Personal Home
Page/Forms Interpreter language was known as PHP/FI. At the time, PHP/FI's main focus was solving
small-time web tasks: guest books, hit counters, and basic forms processing. Its major benefit was
its simplicity; PHP/FI made it easy to handle all the messy tasks thrown at a web developer.
Additionally, it's C-like syntax was already understood by many programmers.

Over the next two years, PHP/FI gradually grew in popularity. However, by 1997, PHP/FI was already
showing its age. As the Internet gathered steam, programmers began to create more complex
applications, such as e-commerce shopping carts. PHP/FI's quirks and limitations hindered
development. It was too slow and was missing some basic features, such asfor and foreach loops.

These problems caused Zeev Suraski and Andi Gutmans, of Zend fame, to begin work on a new
version of PHP/FI. This version, which became PHP 3, solved many difficulties faced by PHP/FI
developers while remaining true to the essential nature of PHP/FI.

In particular, PHP 3 was faster and more efficient than PHP/FI. The new parser also provided the
opportunity to iron out some language oddities, making PHP more consistent. PHP 3's other major
advance was an easy-to-use extension API. Developers from all over contributed extensions to PHP,
effectively turning PHP from a programming language into an entire web development environment.

PHP 3 retained PHP/FI's procedural syntax, but it also introduced a very simplistic object-oriented
syntax. Originally the result of a weekend hack, developers flocked to objects, much to the surprise
of Zeev and Andi. Unfortunately, PHP 3 was ill-equipped to provide all the object-oriented features
developers demanded.

A few months after PHP 3 went final in June of 1998, work started on PHP 4. Again, the problem was
speed. The new extension infrastructure provided the opportunity to create larger and more complex
web sites than ever imagined. In the words of Alan Greenspan, "irrational exuberance" was at hand.

While PHP 4 provided yet another burst of power, its secondary objective was backward compatibility.
There was a strong emphasis on not breaking PHP 3 scripts under PHP 4. As a result, beefing up the
core language was not a main focus of PHP 4. PHP 4 was released in May 2000, almost two years
after PHP 3.

A lot has happened over the last four years. Perl may be the "duct tape of the Internet," but PHP is
the real glue that holds the Web together. However, PHP still faces challenges. The problems of
performance and flexibility have long been conquered, but now PHP is under attack from the twin
foes of Java and C#.

Over the past 10 years, Java and C# have introduced advanced object-oriented programming
concepts to web development. Yet in many ways, despite all the improvements, PHP 4.3 is still the
same procedural language that Rasmus wrote a decade ago. PHP 5 finally grants developers their
wish, providing a full set of object-oriented features.

These features, which are discussed in Chapter 2, allow developers to more easily develop large-scale
applications without resorting to the cumbersome workarounds necessary in PHP 4. They also let you
write cleaner code that's less error-prone and more maintainable.

< Day Day Up >


< Day Day Up >

1.2 What's New in PHP 5?


Better object-oriented features aren't the only new features of PHP 5. Many extensions have been
rewritten to take advantage of PHP 5's new capabilities, and many new extensions have been added
to the distribution.

1.2.1 MySQL
The MySQL database is PHP's partner in crime. Many developers power their web sites with MySQL,
yet PHP's original MySQL extension dates back to the days of PHP/FI. It's showing its age.

In retrospect, some design decisions for the MySQL extension weren't the best solution after all. Also,
the latest versions of MySQL, 4.1 and 5.0, introduce many new features, some of which require
significant changes to the extension.

As a result, PHP 5 comes with a completely new and improved MySQL extension.Dubbed MySQLi, for
the MySQL Improved extension, MySQLi offers prepared statements, bound parameters, and SSL
connections. It even takes advantage of PHP 5's new object-oriented support to provide an OO
interface to MySQL. This extension is covered in Chapter 3.

1.2.2 SQLite
While MySQL is greater than ever, it's actually "too much database" for some jobs. SQLite is an
embedded database library that lets you store and query data using an SQL interface without the
overhead of installing and running a separate database application. It's the topic ofChapter 4.

PHP 5 bundles SQLite, providing developers with a database that's guaranteed to work on all PHP 5
installations. Despite the name, SQLite is a nowhere close to a "lite" database. It supports
transactions, subqueries, triggers, and many other advanced database features.

Like MySQLi, SQLite also comes with dual procedural and OO interfaces.

1.2.3 XML
XML is a key part of web development, so PHP 5 offers a full selection of new XML tools. A major goal
of XML in PHP 5 is interoperability among each of the different XML extensions, making them a
unified unit instead of individual agents.

The new Document Object Model (DOM) extension is leaps and bounds better than PHP 4's
experimental version. It also uses new PHP 5 features to comply with the DOM specification, fulfilling
the goal of DOM as a language-neutral API for XML.
There's also a new Extensible Stylesheet Language Transformations (XSLT) extension that operates
on XML documents and DOM objects. You can transform DOM objects using XSLT and receive
translated documents from XSLT. You can even pass XML nodes back and forth between XSLT and
PHP from within a stylesheet.

Perhaps the most innovative part of PHP 5 is the SimpleXML extension. This lightweight interface to
XML lets you easily iterate through XML documents without the overhead of DOM or the oddness of
XSLT. It's perfect for documents where you know the specification and want to quickly extract data.

Chapter 5 covers all these topics, plus the original Simple API for XML (SAX) extension and an XPath
extension, used for querying XML documents. If you're new to XML or want a refresher on some of its
more difficult parts, such as namespaces, be sure to read Appendix A.

1.2.4 Iterators and SPL


Iterators are a completely new PHP 5 feature and the topic of Chapter 6. They allow you to use a
foreach loop to cycle through different types of data: directory listings, database results, and even
XML documents.

SPL-the Standard PHP Library-is a collection of iterators that provide this functionality and also
filter, limit, cache, and otherwise modify iterator results. Iterators are an incredibly handy way to
abstract away messy details from your code.

1.2.5 Error Handling and Debugging


PHP 5 offers a completely different model of error checking than what's available in PHP 4. It's called
exception handling. With exceptions, you're freed from the necessity of checking the return value of
every function. Instead, you can separate programming logic from error handling and place them in
adjoining blocks of code.

Exceptions are commonly found in object-oriented languages such as Java and C++. When used
judiciously, they streamline code, but when used willy-nilly, they create spaghetti code.

Chapter 7 covers exceptions.

1.2.6 Streams, Filters, and Wrappers


Streams allow you to place a file interface on reading and writing data using protocol-specific objects
known as wrappers. Streams also let you modify the data flowing through them by attachingfilters.

First introduced in PHP 4.3, streams are an underutilized part of PHP. PHP 5 expands upon the
existing streams infrastructure to allow more extensive wrappers, and to let you create filters directly
in PHP instead of in C.

Chapter 8 demonstrates how to create a shared memory wrapper and two streams, one for encoding
HTML entities and another for decoding them.
1.2.7 Other Extensions
Chapter 9 covers three new pieces of PHP 5: SOAP, Tidy, and the Reflection classes.

SOAP is a key component of the fast-growing web services field. This extension lets developers create
SOAP clients with or without a Web Services Description Language (WSDL) file, and also implement
SOAP servers in PHP.

The Tidy extension makes it easy to ensure that your output is valid HTML and XHTML. Its smart
parser brings even the most unruly of files into compliance with the latest W3C specifications.

Nothing less than complete code introspection is the goal of the Reflection classes. This set of classes
lets you examine classes, methods, parameters, and more, to discover object attributes.

It is now simple and easy to create PHP class browsers, debuggers, and other tools that rely on
gathering details about objects and functions.

1.2.8 PHP 5 in Action


It's one thing to see each of the individual parts of PHP 5 in isolation; it's another to see them in
harmony. Chapter 10 provides you with a small address book application that combines the new PHP
5 features into a complete unit.

This chapter pulls together the concepts introduced in the rest of the book and provides you with an
example that shows exactly why application development in PHP 5 is so great. Even in a short
program, you can take advantage of SQLite, DOM, SimpleXML, iterators, abstract classes,
exceptions, and property overloading to create an application that's flexible in many dimensions, yet
easy to write and understand.

This address book lets you add contact information and search stored records using both a web and
command-line interface. The output is completely separated from application logic, and the program
uses a simple series of template classes to control its formatting.

1.2.9 Everything Else


PHP 5 is such a major update that it's impossible to cover it completely in only 10 chapters.
Additionally, some features are so minor, they're only worth mentioning briefly.

Appendix B contains all the small changes and fixes that aren't mentioned in the chapters. It's
definitely worth a read because, in many ways, it's easiest to be tripped up by minor changes. You
know you're on new ground with the mysqli extension, but, unless you read Appendix B, you
probably would not know that you can now pass optional parameters by reference, or thatstrrpos(
) behaves slightly differently than before, or even that the command-line version of PHP has the
ability to execute code on every line of input.

< Day Day Up >


< Day Day Up >

1.3 Installing and Configuring PHP 5


You can download PHP 5 from http://www.php.net/downloads.php. The installation process is
identical to PHP 4; however, PHP 5 does have some new configuration options. SeeTable C-1 in
Appendix C for a complete list.

During the transition from PHP 4 to PHP 5, it's common to want to run both versions of PHP
simultaneously. This lets you easily test code under PHP 5 without switching completely away from
PHP 4. It also allows you to slowly migrate scripts from PHP 4 to PHP 5, as you can control which
version of PHP parses specific sets of pages.

One option is to run two separate web servers, either on different machines or on different ports on
the same machine. The primary advantage of using two web servers is that you can run both PHP 4
and PHP 5 as a module. The disadvantages are that you either need multiple computers at your
disposal or need to be familiar with installing Apache (or your particular web server of choice). It's
also a lot of work for a temporary situation.

Another solution is to continue using your existing web server and install one version of PHP as a
module and another as a CGI. This gets you up and running with both versions with minimal fuss;
however, the CGI version of PHP lacks certain features included in the Apache module.

Appendix C provides detailed instructions for setting up PHP 4 and PHP 5 under Apache on both Unix
and Windows. It shows how to configure Apache to switch between the versions both on a directory-
by-directory basis and by setting up a virtual server on another port.

< Day Day Up >


< Day Day Up >

Chapter 2. Object-Oriented Programming


This chapter introduces object-oriented programming (OOP) and explains all the object-oriented (OO)
features in PHP 5. The chapter assumes no prior knowledge of OOP, so if this is your first time
learning it, that's okay.

However, there's lots here for PHP 4 OO programmers, too. PHP 5, besides adding many OO bells
and whistles, modifies fundamental parts of PHP 4's OO behavior. Running PHP 4 programs under
PHP 5 will result in unexpected results and errors if you're not up-to-date on all the changes.

Additionally, the new features allow you to implement many OOP best practices that just aren't
possible in PHP 4. This chapter shows you how and why you should modify your existing code to take
full advantage of PHP 5.

Early versions of PHP were strictly procedural: you could define functions, but not objects. PHP 3
introduced an extremely rudimentary form of objects, written as a late-night hack. Back in 1997,
nobody expected the explosion in the number of PHP programmers, nor that people would write
large-scale programs in PHP. Therefore, these limitations weren't considered a problem.

Over the years, PHP gained additional object-oriented features; however, the development team
never redesigned the core OO code to gracefully handle objects and classes. As a result, although
PHP 4 improved overall performance, writing complex OO programs with it is still difficult, if not
nearly impossible.

PHP 5 fixes these problems by using Zend Engine 2. The first version of the Zend Engine was written
for PHP 4 to handle PHP's core functionality, such as what type of objects you can use, and to define
the language's syntax.

Zend Engine 2, which powers PHP 5, enables PHP to include more advanced object-oriented features,
while still providing a high degree of backward compatibility to the millions of PHP scripts already
written.

If you don't have experience with object-oriented programming outside of PHP, then you're in for a
bit of a surprise. While some of the new features allow you to do things more easily, many features
don't let you do anything new at all. In many ways, they restrict what you can do.

Even though it seems counterintuitive, these limitations actually help you quickly write safe code
because they promote code reuse and data encapsulation. These key OO programming concepts are
explained throughout the chapter.

< Day Day Up >


< Day Day Up >

2.1 What Is Object-Oriented Programming?


Object-oriented programming is a way to group functions and data together into a prepackaged unit.
This unit is known as an object.

Many people prefer OOP because it encourages a behavior known as encapsulation. Inevitably,
whenever you write code, there's some part-the way you store the data, what parameters the
functions take, how the database is organized-that doesn't work as well as it should. It's too slow,
too awkward, or doesn't allow you to add new features, so you clean it up.

Fixing code is a good thing, unless you accidently break other parts of your system in the process.
When a program is designed with a high degree of encapsulation, the underlying data structures and
database tables are not accessed directly. Instead, you define a set of functions and route all your
requests through these functions.

For example, you have a database table that stores names and email addresses. A program with
poor encapsulation directly accesses the table whenever it needs to fetch a person's email address:

$name = 'Rasmus Lerdorf';

$db = mysql_connect( );

$result = mysql_query("SELECT email FROM users

WHERE name LIKE '$name'", $db);

$row = mysql_fetch_assoc($r);

$email = $row['email'];

A better-encapsulated program uses a function instead:

function getEmail($name) {

$db = mysql_connect( );

$result = mysql_query("SELECT email FROM users

WHERE name LIKE '$name'", $db);

$row = mysql_fetch_assoc($r);

$email = $row['email'];

return $email
}

$email = getEmail('Rasmus Lerdorf');

Using getEmail( ) has many benefits, including reducing the amount of code you need to write to
fetch an email address. However, it also lets you safely alter your database schema because you only
need to change the single query in getEmail( ) instead of searching through every line of every file,
looking for places where you SELECT data from the users table.

It's hard to write a well-encapsulated program using functions, because the only way to signal to
people "Don't touch this!" is through comments and programming conventions.

Objects allow you to wall off implementation internals from outside access. This prevents people from
relying on code that may change and forces them to use your functions to reach the data. Functions
of this type are known as accessors, because they allow access to otherwise protected information.
When redesigning code, if you update the accessors to work as before, none of the code will break.

More information on encapsulation appears later, but first, here's an introduction to using objects in
PHP 5.

2.1.1 Using Objects


Typically, objects represent "real-world" or tangible entities, such as a person. Here is one version of
a Person object in PHP:

$rasmus = new Person;

$rasmus->setName('Rasmus Lerdorf');

print $rasmus->getName( );

Rasmus Lerdorf

The first line assigns a value to a $rasmus variable. This value is an object of type Person. Person is
a previously specified structure containing code that describes how a "Person object" should operate.
This structure is called a class.

The difference between an object and a class is that an object is an actual variable that you can
manipulate. You can pass it to functions, delete it, copy it, and so forth. It holds a specific set of data.

A class is the template that defines how the object can be used and what data it can hold.

Convert a class into an object by using the new keyword:

$rasmus = new Person;


This command causes PHP to look for a class labeled as Person, create a new copy, and assign it to
$rasmus. This process is known as instantiating an object or creating a new instance of a class.

For now, don't worry about the actual syntax for defining Person. Also, it's not necessary to know
how Person stores data. This information is encapsulated away, and you're forced to work without it.
(And this is a good thing!)

What you do need to know is that Person allows you to call something that looks like a function
called setName( ):

$rasmus->setName('Rasmus Lerdorf');

When you define a class, you can specify functions that belong to the class. To call an object's
function, place an arrow (->) after the object and then add the function name. This tells PHP to
invoke the setName( ) function on that specific instance of the class.

The proper term for setName( ) isn't "function"; it's a method or an object method. Here's how you
use the term in a full sentence: "I called the setName( ) method on the object," or "You must call
the object's setName( ) method."

The setName( ) method sets the "name" attribute of $rasmus. In this case, it's set to Rasmus
Lerdorf.

You can retrieve this value by calling getName( ):

print $rasmus->getName( );

Rasmus Lerdorf

The getName( ) method looks up the value stored by an earlier call to setName( ) and returns it.
Because of encapsulation, you don't know how Person stores this data-the specific details are
irrelevant.

In object-oriented programming, there is an implicit contract between the class's author and the
users of the class. The users agree not to worry about the implementation details. The author agrees
that as long as a person uses accessor methods, such as setName( ) and getName( ), they'll always
work, even if the author redesigns the class.

The full details on class creation come later, but here's a first look at the elements of a simple class.
For example, the Person class can look like this:

class Person {

setName($name) {

$this->name = $name;

}
getName( ) {

return $this->name;

You define and name a class like you define and name a function, except that you use the word
class instead of function, and parentheses (( )) don't follow the class name.

Inside the class, declare class methods like you declare regular functions:

function setName($name) {

$this->name = $name;

function getName( ) {

return $this->name;

These two methods store and return a name using a special class variable called$this. That's why
$rasmus->getName( ) is able to remember and return the value passed in by $rasmus-
>setName('Rasmus Lerdorf').

That's all for now on writing classes. It's time to go back to using classes and objects.

In PHP 5, you can call a method on an object returned by a function:

function getRasmus( ) {

$rasmus = new Person;

$rasmus->setName('Rasmus Lerdorf');

return $rasmus;

print getRasmus( )->getName( );

Rasmus Lerdorf
This isn't possible in PHP 4. Instead, you store the object in a temporary variable as an intermediate
step:

function getRasmus( ) {

$rasmus = new Person;

$rasmus->setName('Rasmus Lerdorf');

return $rasmus;

$rasmus = getRasmus( )

print $rasmus->getName( );

Calling setName( ) on different objects causes that method to operate on a different set of data.
Each instance operates independently of every other instance, even if they're of the same class.

$rasmus = new Person;

$zeev = new Person;

$rasmus->setName('Rasmus Lerdorf');

$zeev->setName('Zeev Suraski');

print $rasmus->getName( );

print $zeev->getName( );

Rasmus Lerdorf

Zeev Suraski

This example creates two instances of the Person class, $rasmus and $zeev. These objects are
separate, so the call $zeev->setName('Zeev Suraski'); doesn't undo the earlier call of $rasmus-
>setName('Rasmus Lerdorf');.

Besides methods, objects also have properties. A property is to an object as an element is to an


array. You refer to it by a name and can store in it every type of data: strings, arrays, and even
other objects.

The syntax for property access is like method access, except that there are no parentheses following
the property name:

$rasmus = new Person;

$rasmus->name = 'Rasmus Lerdorf';

print $rasmus->name;

Rasmus Lerdorf

This assigns the string Rasmus Lerdorf to the name property of $rasmus. Then, it retrieves the string
and prints it out. An object with only properties and no methods is more or less a fancy array.

2.1.2 Autoload
When you attempt to instantiate a class that's not defined, PHP 4 dies with a fatal error because it
can't locate what you're looking for. PHP 5 solves this problem by loading the missing code on the fly
with its new autoload feature.

Extensive use of classes requires you to either define all your classes in a single file or else place an
include statement for each class you use at the top of every script. Since PHP 5 calls _ _autoload(
) when you instantiate undefined classes, you can make it include all the classes used by your script
with only a little work:

function _ _autoload($class_name) {

include "$class_name.php";

$person = new Person;

The _ _autoload( ) function receives the class name as its single parameter. This example appends
a .php extension to that name and tries to include a file based on $class_name. So, when you
instantiate a new Person, it looks for Person.php in your include_path.

If you adopt the PEAR-style naming convention of placing an underscore between words to reflect the
file hierarchy, use the code in Example 2-1.

Example 2-1. Autoloading classes using PEAR naming conventions

function _ _autoload($package_name) {
// split on underscore

$folders = split('_', $package_name);

// rejoin based on directory structure

// use DIRECTORY_SEPARATOR constant to work on all platforms

$path = join(DIRECTORY_SEPARATOR, $folders);

// append extension

$path .= '.php';

include $path;

With the code in Example 2-1, you can do the following:

$person = new Animals_Person;

If the class isn't defined, Animals_Person gets passed to _ _autoload( ). The function splits the
class name on underscore ( _ ) and joins it on DIRECTORY_SEPARATOR. This turns the string into
Animals/Person on Unix machines (and Animals\Person on Windows).

Next, a .php extension is appended, and then the file Animals/Person.php is included for use.

While using _ _autoload( ) slightly increases processing time during the addition of a class, it is
called only once per class. Multiple instances of the same class does not result in multiple calls to_
_autoload( ).

2.1.3 Data Encapsulation


Although using properties instead of accessor methods is less work up front, it's not a good idea,
because it reduces encapsulation. Reading and writing toname directly instead of calling setName( )
and getName( ) undermines the layer of abstraction that prevents code from breaking after
redesigns. Since this is a big benefit of object-oriented programming, you should avoid using
properties instead of accessor methods.

PHP 5 allows you to enforce the distinction between what should and shouldn't be accessed directly.
All of the methods and properties shown so far have been public methods and properties. This means
anyone can call or edit them.

In PHP 4, all properties and methods are public. In PHP 5, however, you can use theprivate label to
restrict access to only those methods defined inside the class. When this label is applied to a method
or property, it is known as private. Marking something as private signals that it may change in the
future, so people shouldn't access it or they'll violate encapsulation.

This is more than a social convention. PHP 5 actually prevents people from calling a private method
or reading a private property outside of the class. Therefore, from an external perspective, these
methods and properties might as well not exist because there's no way to access them. More
information on access control appears in Section 2.3.3, later in this chapter.

2.1.4 Constructors and Destructors


Objects in PHP 5 also have the ability to call constructors and destructors. Aconstructor is a method
that is called automatically when an object is instantiated. Depending upon how the constructor is
implemented, you may be able to pass it arguments.

For example, a constructor for a class that represents a database may take the address of the
database you wish to connect to, as well as the username and password necessary for
authentication:

$db = new Database('db.example.com', 'web', 'jsd6w@2d');

This creates an new instance of a Database class and passes three pieces of information to the
constructor. The class's constructor will use that data to create a connection to the database and
then store the result handle in a private property.

PHP 4 has object constructors, but object destructors are new to PHP 5. Destructors are like
constructors, except that they're called when the object is deleted. Even if you don't delete the object
yourself using unset( ), PHP 5 still calls the destructor when it determines that the object is no
longer used. This may be when the script ends, but it can be much earlier.

You use a destructor to clean up after an object. For instance, the Database destructor would
disconnect from the database and free up the connection. Unlike constructors, you cannot pass
information to a destructor, because you're never sure when it's going to be run.

< Day Day Up >


< Day Day Up >

2.2 Memory Management


In PHP 4, when you copy a variable or pass it to a function, you don't transfer the original variable.
Instead, you pass a copy of the data stored in the variable. This is known aspass-by-value because
you're copying the variable's values and creating a duplicate.

As a result, the new variable is completely disassociated from the original. Modifying one doesn't
affect the other, similar to how calling $zeev->setName( ) didn't affect $rasmus in the earlier
example.

2.2.1 Object References


Objects in PHP 5 behave differently from other variables. You don't pass them by value, like you do
with scalars and arrays. Instead, you pass them by reference. A reference, or object reference, is a
pointer to the variable. Therefore, any alterations made to the passed object are actually made to the
original.

Here's an example:

$rasmus = new Person;

$rasmus->setName('Rasmus Lerdorf');

$zeev = $rasmus;

$zeev->setName('Zeev Suraski');

print $rasmus->getName( );

Zeev Suraski

In this case, modifying $zeev does change $rasmus!

This is not what occurs in PHP 4. PHP 4 prints Rasmus Lerdorf because $zeev = $rasmus causes
PHP to make a copy of the original object and assign it to $zeev.

However, in PHP 5, this command assigns $zeev a reference to $rasmus. Any changes made to
$zeev are actually made to $rasmus.

A similar behavior occurs when you pass objects into functions:


function editName($person, $name) {

$person->setName($name);

$rasmus = new Person;

$rasmus->setName('Rasmus Lerdorf');

setName($rasmus, 'Zeev Suraski');

print $rasmus->getName( );

Zeev Suraski

Normally, changes made inside of editName( ) don't alter variables outside of the function, and to
update the original object you need to return the modified variable. That's what you need to do in
PHP 4.

Since PHP 5 passes objects by reference, changing them inside a function or a method alters the
original object. There's no need for you to pass them explicitly by reference or return the modified
copy. This action is also referred to as passing an object handle, since "handle" is a synonym of
reference or pointer.

Other variable types, including strings and arrays, are still passed by value by default, unless
declared otherwise in the function prototype by placing an ampersand (&) before the variable name.

This change in PHP 5 makes it much easier to use objects because it's far more common to need to
pass objects by reference than by value. When your data is encapsulated inside of objects, you
frequently pass an instance or two to a method and alter the objects inside of the method.

Without this switch, to cleanly propagate these changes back to the original instances you must place
ampersands everywhere you need PHP to pass the objects by reference. However, if you omit even a
single ampersand, you end up with a bug that's difficult to isolate and broken code.

To copy the data inside an object and not just the reference to it, instead of a direct assignment
using an equals sign (=), use the clone operator:

$rasmus = new Person;

$rasmus->setName('Rasmus Lerdorf');

$zeev = clone $rasmus;


$zeev->setName('Zeev Suraski');

print $rasmus->getName( );

print $zeev->getName( );

Rasmus Lerdorf

Zeev Suraski

This tells PHP not to assign a reference, but to duplicate the values stored in$rasmus and store them
in a new object that gets assigned to $zeev. This also means $ramsus and $zeev are independent
entities, so calling $zeev->setName( ) doesn't update $rasmus.

PHP 4 programs that rely heavily on PHP 4's pass- and copy-by-value behaviors can enable the
zend.ze1_compatibility_mode configuration directive. This causes PHP 5 to clone objects instead of
using a reference.

This directive also re-enables a few quirks of PHP 4 that have been ironed out in PHP 5.For instance,
you can no longer cast an object to an integer or floating-point number. In PHP 4, objects with
properties become 1 and objects without properties become 0.

Turning on the compatibility mode may ease your transition to PHP 5, but it should not be a long-
term solution. It reduces application portability, and you cannot share code between applications that
enable the compatibility mode and those that assume it's disabled. New sites should be developed
with this directive set to its default (Off).

2.2.2 Garbage Collection


Some languages, most notably C, require you to explicitly request memory from the computer
whenever you create strings or data structures. Once you've allocated the memory, you can then
store information in the variable.

You're also responsible for releasing, or freeing, the memory when you're finished using the variable.
This allows the machine to provide the memory to other variables in your program and prevents your
computer from running out of RAM. This is also a big pain.

PHP handles memory allocation for you. When you instantiate an object, it automatically doles out
memory. When it's deleted, it frees the memory.

The process of cleaning up unused objects is known as garbage collection. The type of garbage
collection used by PHP is called reference counting.

When you create a value-such as a string, number, or object-PHP records its existence and sets a
counter to one, indicating that there's one copy of the value. From then on, PHP tracks the value,
incrementing and decrementing its counter accordingly.

The counter goes up by one when you create a reference to the value, either by passing it into a
function by reference or by assigning it by reference to another variable. (Objects are always
assigned by reference, unless you use clone; non-objects are assigned by reference using the =&
operator.) The counter goes down by one when you remove a reference to a value, which happens
when you exit a function or delete the variable. For example:

$rasmus1 = new Person( ); // New object: Reference count = 1

$rasmus2 = $rasmus1; // Copy-by-reference: Reference count = 2

unset($rasmus1); // Delete a reference: Reference count = 1

sendEmailTo($rasmus); // Pass-by-reference:

// During function execution:

// Reference count = 2

// After function completes:

// Reference count = 1

unset($rasmus2); // Delete a reference: Reference count = 0

When the counter hits zero, PHP knows the object is no longer used anywhere in your program, so
deletes it and releases its memory. Before it does that, however, PHP calls your object's destructor to
let you clean up any higher-level resources you've opened in the object.

At the end of a script, PHP cleans up all the remaining values that still have a nonzero reference
count.

< Day Day Up >


< Day Day Up >

2.3 Basic Classes


To define a class, use the class keyword followed by the class name:

class Person {

This code creates a Person class. This is not a very exciting class, because it lacks methods or
properties. Class names in PHP are case-insensitive, so you cannot define both a Person and a
PERSON class.

Use this class name to instantiate a new instance of your object:

$rasmus = new Person;

Alternatively, to determine a class from an object instance, useget_class( ):

$person = new Person;

print get_class($person)

Person

Even though class names are case-insensitive, PHP 5 preserves their capitalization. This is different
from PHP 4, where PHP converts the name to lowercase. In PHP 4, calling get_class( ) on an
instance of the Person class produced person. PHP 5 returns the correct class name.

2.3.1 Properties
List class properties at the top of the class:

class Person {

public $name;

This creates a public property named name. When a property is public, it can be read from and
written to anywhere in the program:

$rasmus = new Person;

$rasmus->name = 'Rasmus Lerdorf';

Properties in PHP 4 are declared using a different syntax: var. This syntax is deprecated in favor of
public, but for backward compatibility, var is still legal. The behavior of a property declared using
public and var is identical.

Never use public properties. Doing so makes it easy to violate encapsulation. Always use accessor
methods instead.

You don't need to predeclare a property inside the class to use it. For instance:

$rasmus = new Person;

$rasmus->email = '[email protected]';

This assigns [email protected] to the email property of $rasmus. This is valid, even though email was
not mentioned in the class definition.

Even though you don't have to, always predeclare your properties. First, these properties are
implicitly public, so they're already bad. Second, predeclaring properties forces you to think about the
best way to handle data. It also makes it easier for anyone reading the class (including yourself two
months later) to see all of the class's properties without wading through the entire code of the class.

2.3.2 Methods
Define methods underneath properties. They're declared using the standard function syntax:

class Person {

public $name;

public function setName($name) {

$this->name = $name;

The public keyword indicates that anyone can call the setName( ) method. In PHP 4, methods are
not preceded by a visibility identifier such as public. For backward compatibility, these methods are
assumed to be public.
Unlike properties, public methods are okay to use. Accessor methods, for instance, are frequently
declared as public.

To refer to an object instance inside of a class, use the special variable$this. For example:

public function setName($name) {

$this->name = $name;

In this code, the setName( ) method sets the name property of the current object to the value of the
$name variable passed into the method.

Be careful not to place a dollar sign before the property name, such as$this->$name. This causes
PHP to access the property with the value stored in the $name variable. This is occasionally the
desired result, but often is not.

PHP 4 doesn't prevent you from assigning a new object to $this:

public function load($object) {

$this = $object;

but this is illegal in PHP 5. You can only alter object properties. Trying to set$this to a new value
produces an error:

PHP Fatal error: Cannot re-assign $this

2.3.3 Access Restrictions


To prevent a property or method from being accessed from outside a class, use theprivate
keyword:

class Person {

private $name;

public function setName($name) {

$this->name = $name;

}
}

$rasmus = new Person;

$rasmus->setName('Rasmus Lerdorf');

print $rasmus->name;

PHP Fatal error: Cannot access private property Person::$name... on line 12

When name is declared as private, it cannot be accessed outside of the class. It's still safe to
manipulate it inside of setName( ) because that's an internal method. What you cannot do is
something like:

print $rasmus->name;

This causes a fatal error, which is why you need to implement a getName( ) method:

class Person {

private $name;

public function setName($name) {

$this->name = $name;

public function getName( ) {

return $this->name;

$rasmus = new Person;

$rasmus->setName('Rasmus Lerdorf');

print $rasmus->getName( );
Rasmus Lerdorf

This code works as expected and prints Rasmus Lerdorf.

Declare private methods by placing the word private in front of function:

class Person {

private $email;

public function setEmail($email) {

if ($this->validateEmail($email)) {

$this->email = $email;

private function validateEmail($email) {

// email address validation

// regular expression

// omitted for clarity

$rasmus = new Person;

$rasmus->setEmail('[email protected]');

This code declares two methods, one public and one private. The public method,setEmail( ), lets
you set a person's email address. The private method, validateEmail( ), is used internally by the
class to check whether the address is valid. Since that method is not relevant to a user, it is declared
as private.

This example also shows how to access a method from within a class. The syntax is akin to accessing
a class's property. Use $this to represent the object, as is done inside setEmail( ):
public function setEmail($email) {

if ($this->validateEmail($email)) {

$this->email = $email;

This code calls the validateEmail( ) method of the Person class and passes it the $email variable.
Since this call occurs in a method defined in the same class, it works even thoughvalidateEmail( )
is declared as private.

2.3.4 Constructors and Destructors


Object constructors act the same in PHP 4 and PHP 5, but PHP 5 introduces a new naming
convention. In PHP 4, an object's constructor has the same name as its class:

class Database {

function Database($host, $user, $password) {

$this->handle = db_connect($host, $user, $password);

$db = new Database('db.example.com', 'web', 'jsd6w@2d');

Creating a new instance of the Database class causes PHP to call the Database( ) method.

To designate an object constructor in PHP 5, name the method _ _construct( ):

class Database {

function _ _construct($host, $user, $password) {

$this->handle = db_connect($host, $user, $password);

}
$db = new Database('db.example.com', 'web', 'jsd6w@2d');

To ease the transition from PHP 4, if PHP 5 cannot find a method named _ _construct( ) within
your object hierarchy, it reverts to the PHP 4 constructor naming scheme and searches accordingly.
Since a PHP 4 constructor has the same name as its class, unless you have class methods called_
_construct( ) that are serving another purpose, your existing code should not break under PHP 5.
(The reason for this name change is discussed in Section 2.5.4, later in this chapter.)

There are no backward compatibility issues with destructors, because they aren't available in PHP 4.
However, that doesn't mean people didn't try to recreate them using other language features. If you
emulated destructors, you will want to port your code, because PHP 5's destructors are more efficient
and easier to use.

In PHP 4, you can mimic destructors by defining a method that you want to act as a destructor and
then using register_shutdown_function( ) to make PHP invoke it at the end of the script, as in
Example 2-2.

Example 2-2. Mimicking destructors in PHP 4

register_shutdown_function('destruct');

$GLOBALS['objects_to_destroy'] = array( );

function destruct( ) {

foreach($GLOBALS['objects_to_destroy'] as $obj) {

$obj->destruct( );

class Database {

function database($host, $user, $password) {

$this->handle = db_connect($host, $user, $password);

$GLOBALS['objects_to_destroy'][ ] = &$this;

function destruct( ) {
db_close($this->handle); // close the database connection

PHP has a special function, register_shutdown_function( ), that's called by PHP right before the
script ends. You can use register_shutdown_function( ) to ensure PHP runs whatever clean-up
code you want before it does its own housekeeping.

Example 2-2 sets things up so that PHP calls the destruct( ) function. This function iterates through
a list of destroyable objects stored in the global variable $objects_to_destroy and calls each
object's destruct( ) method.

When an object needs a destructor, it must add itself to the $objects_to_destroy array and also
implement a destruct( ) method. The destruct( ) method should contain any code needed to
clean up the resources that were consumed when the object was created and used.

In this example, the Database class adds itself in the constructor by doing
$GLOBALS['objects_to_destroy'][ ] = &$this; . This ensures all objects are properly accounted
for. Its destruct( ) method calls db_close( ) to shut the database connection.

In many cases, such as closing connections to databases and unlocking files, PHP will do this for you
automatically. However, this clean up occurs only when the script is complete. Therefore, it's a good
idea to release them yourself in the destructor. It's best to clean up as soon as possible because
other programs may want to use your database connection or access the locked file.

When the majority of PHP scripts were short and fast, letting PHP tidy up your mess wasn't a large
concern, because the time between the moment you finished using the resource and the end of the
script was very short. Now that PHP is used on the command line and for more complex tasks, long-
running scripts are commonplace, so it's a practical concern.

You may notice that since this implementation of destructors uses register_shutdown_function(
), there's no time benefit, because the destructor isn't called until the end of the script. This is one of
the big differences between destructors in PHP 5 and this PHP 4 emulation.

In PHP 5, objects are destroyed when they're no longer used, so connections are freed earlier. Also,
this implementation is far from clean and object-oriented. You need to use global variables and global
functions to track your objects, and it's easy to break the scheme by overwriting the array.

Thankfully, PHP 5 implements object destructors in the language itself, so PHP automatically tracks
which objects have destructors and calls them as soon as you're finished using the object. This can
be much earlier than when your program ends, so resources such as database connections and file
locks won't be held open for the entire script; instead, they are released as soon as possible.

Like constructors, destructors in PHP 5 have a fixed name: _ _destruct( ). Since you don't call the
destructor manually, you're not allowed to pass it any parameters. If your destructor needs any
object-specific information, store it as a property:

// PHP 5 Destructor
class database {

function _ _destruct( ) {

db_close($this->handle); // close the database connection

Since destructors are now a language-level feature, there's no need for use
register_shutdown_function( ). Everything is done for you.

You cannot assume that PHP will destroy objects in any particular order. Therefore, you should not
reference another object in your destructor, as PHP may have already destroyed it. Doing so will not
cause a crash, but it will cause your code to behave in an unpredictable (and buggy) manner.

< Day Day Up >


< Day Day Up >

2.4 Class Intermediates


The topics in the previous section covered the limit of PHP 4's object-oriented abilities. This section
introduces a few concepts new to PHP 5: interfaces, type hinting, and static methods and properties.

2.4.1 Interfaces
In object-oriented programming, objects must work together. Therefore, you should be able to
require a class (or more than one class) to implement methods that are necessary for the class to
interact properly in your system.

For instance, an e-commerce application needs to know a certain set of information about every item
up for sale. These items may be represented as different classes: Book, CD, DVD, etc. However, you
need to know that your application can find the name, price, and inventory number of each object,
regardless of its type.

The mechanism for forcing classes to support the same set of methods is called aninterface. Defining
an interface is similar to defining a class:

interface Sellable {

public function getName( );

public function getPrice( );

public function getID( );

Instead of using the keyword class, an interface uses the keyword interface. Inside the interface,
define your method prototypes, but don't provide an implementation.

This creates an interface named Sellable. Any class that's Sellable must implement the three
methods listed in the interface: getName( ), getPrice( ), and getID( ).

When a class supports all the methods in the interface, it's calledimplementing the interface. You
agree to implement an interface in your class definition:

class Book implements Sellable {

public function getName( ) { ... }


public function getPrice( ) { ... }

public function getID( ) { ... }

Failing to implement all the methods listed in an interface, or implementing them with a different
prototype, causes PHP to emit a fatal error.

A class can agree to implement as many interfaces as you want. For instance, you may want to have
a Listenable interface that specifies how you can retrieve an audio clip for an item. In this case, the
CD and DVD classes would also implement Listenable, whereas the Book class wouldn't.

When you use interfaces, it's important to declare your classes before you instantiate objects. In PHP
4, you can arrange your code in any order and PHP will still find a the class definition.

In PHP 5, that's still mostly true; however, when a class implements interfaces, PHP 5 can sometimes
become confused. To avoid breaking existing applications, this requirement is not enforced, but it's
best not to rely upon this behavior.

2.4.2 Type Hinting


Another way of enforcing controls on your objects is by usingtype hints. A type hint is a way to tell
PHP that an object passed to a method must be of a certain class.

In PHP 4, you must check that an argument is of the correct type by yourself. This results in many
calls to get_class( ) and is_array( ) inside your code.

To ease this burden, PHP 5 allows you to transfer the job of type checking to PHP. You can optionally
specify a class name in function and method prototypes. This only works for classes, though, not for
any other variable types. You cannot, for example, require that an argument be an array.

For example, to require the first argument to your AddressBook class's add( ) method to be of type
Person:

class AddressBook {

public function add(Person $person) {

// add $person to address book

Then, if you call add( ) but pass a string, you get a fatal error:
$book = new AddressBook;

$person = 'Rasmus Lerdorf';

$book->add($person);

PHP Fatal error: Argument 1 must be an object of class Person in...

Placing a type hint of Person in the first argument of your function declaration is equivalent to adding
the following PHP code to the function:

public function add($person) {

if (!($person instanceof Person)) {

die("Argument 1 must be an instance of Person");

The instanceof operator checks whether an object is an instance of a particular class. This code
makes sure $person is a Person.

PHP 4 does not have an instanceof operator. You need to use the is_a( ) function, which is
deprecated in PHP 5.

Type hinting has the side benefit of integrating API documentation directly into the class itself. If you
see that a class constructor takes an Event type, you know exactly what to provide the method.
Additionally, you know that the code and the "documentation" must always be in sync, because it's
baked directly into the class definition.

However, type hinting does come at the cost of less flexibility. There's no way to allow a parameter to
accept more than one type of object, so this places some restrictions upon how you design your
object hierarchy.

Also, the penalty for violating a type hint is quite drastic-the script aborts with a fatal error. In a
web context, you may want to have more control over how errors are handled and recover more
gracefully from this kind of mistake. Implementing your own form of type checking inside of methods
lets you print out an error page if you choose.

Last, unlike some languages, you cannot use type hinting for return values, so there's no way to
mandate that a particular function always returns an object of a particular type.

2.4.3 Static Methods and Properties


Occasionally, you want to define a collection of methods in an object, but you want to be able to
invoke those methods without the hassle of instantiating a object. In PHP 5, declaring a method
static lets you call it directly:

class Format {

public static function number($number, $decimals = 2,

$decimal = ',', $thousands = '.') {

return number_format($number, $decimals, $decimal, $thousands);

print Format::number(1234.567);

1,234.57

Since static methods don't require an object instance, use the class name instead of the object. Don't
place a dollar sign ($) before the class name.

Static methods aren't referenced with an arrow (->), but with double colons (::)-this signals to PHP
that the method is static. So, in the example, the number( ) method of the Format class is accessed
using Format::number( ).

Number formatting doesn't depend upon any other object properties or methods. Therefore, it makes
sense to declare this method static. This way, for example, inside your shopping cart application,
you can format the price of items in a pretty manner with just one line of code and still use an object
instead of a global function.

Static methods do not operate on a specific instance of the class where they're defined. PHP does not
"construct" a temporary object for you to use while you're inside the method. Therefore, you cannot
refer to $this inside a static method, because there's no $this on which to operate. Calling a static
method is just like calling a regular function.

PHP 5 also has a feature known as static properties. Every instance of a class shares these properties
in common. Thus, static properties act as class-namespaced global variables.

One reason for using a static property is to share a database connection among multipleDatabase
objects. For efficiency, you shouldn't create a new connection to your database every time you
instantiate Database. Instead, negotiate a connection the first time and reuse that connection in
each additional instance:

class Database {

private static $dbh = NULL;


public function _ _construct($server, $username, $password) {

if (self::$dbh = = NULL) {

self::$dbh = db_connect($server, $username, $password);

} else {

// reuse existing connection

$db = new Database('db.example.com', 'web', 'jsd6w@2d');

// Do a bunch of queries

$db2 = new Database('db.example.com', 'web', 'jsd6w@2d');

// Do some additional queries

Static properties, like static methods, use the double colon notation. To refer to a static property
inside of a class, use the special prefix of self. self is to static properties and methods as $this is
to instantiated properties and methods.

The constructor uses self::$dbh to access the static connection property. When $db is
instantiated, dbh is still set to NULL, so the constructor calls db_connect( ) to negotiate a new
connection with the database.

This does not occur when you create $db2, since dbh has been set to the database handle.

< Day Day Up >


< Day Day Up >

2.5 Inheritance
Besides encapsulation, another major advantage of object-oriented code is reuse. Reusing code
reduces development time and the number of bugs. Object-oriented programming promotes reuse
through a process known an inheritance.

2.5.1 Extending Classes


With inheritance, you can modify a class by adding or rewriting its methods. This allows your new
class to be more specific than the original one, while still allowing you access to all the methods of
the first class. The original class is known as the parent, and the new class is called the child.

Creating a child class is also called extending a class or subclassing an object. The original class that's
extended can also be called a super class or a base class.

For instance, you can extend Person to create an Employee class, where an Employee is a Person
with a salary.

When extending a class, abide by the "is a" rule. You should always be able to say, "Child class is a
Parent class." Following this rule leads to clean relationships between your classes. This example is
okay because an Employee is a Person:

class Person {

private $name;

public function setName($name) {

$this->name = $name;

public function getName( ) {

return $this->name;

}
class Employee extends Person {

private $salary;

public function setSalary($salary) {

$this->salary = $salary;

public function getSalary( ) {

return $this->salary;

$billg = new Employee;

$billg->setName('Bill Gates');

$billg->setSalary(865114); // Actual 2003 salary (excluding stock)

This code creates a new Employee class; instantiates a new instance of Employee, $billg; and sets
his name and salary.

The extends keyword at the top of the class definition indicates to PHP that this class should inherit
all the properties and methods of the parent class. This allows you to interact with a child object in
the same way as its parent because when PHP cannot find a property or method in the child class, it
searches the parent class.

Therefore, you can call setName( ) on an Employee, even though this method isn't defined in the
class, because it's defined in Person and Employee extends Person.

Inheritance also lets you modify the parent class. This allows you to create two new methods,
setSalary( ) and getSalary( ), and a property, $salary, to store the salary information.

When using inheritance, a third visibility level, protected, is useful. A public method is callable by
anyone; a private method is callable by any method in the same class, but not by any methods in a
subclass; a protected method, like a private method, restricts access to only the methods within
the class, but it also allows access from children classes.
You should use protected instead of private in your classes, unless you have a particularly strong
reason to deliberately prevent a subclass from accessing a method or property.

In the previous example, it did not matter that the name property was declared private, because it's
altered only by setName( ) and getName( ), and these methods are in the same class as name.
However, you could not add a new method to Employee that references name. This would not be the
case if name were declared as protected.

2.5.2 Class Hierarchies


Inheritance isn't limited to one level. You can further extendEmployee:

class Executive extends Employee {

public function swindleShareholders( ) {

Since an instance of the Executive class inherits everything from Employee, it also inherits
everything from Person. This simple concept allows you to build up a tree of classes that start with
an extremely basic class and become further specialized as they continue to extend each other. This
is known as a class hierarchy.

You can branch classes in your class hierarchy. For instance, in addition to anEmployee line, you can
have a Student class that also extends Person. This results in the hierarchy shown in Figure 2-1.

Figure 2-1. Class hierarchy

Some languages, most notably C++, allow a class to extend multiple parents. This is known as
multiple inheritance. Multiple inheritance is handy when your child needs to take on characteristics of
two different sets of parents.

For instance, consider an object that is an Employee and a Parent. Many people fit both criteria, but
it doesn't make sense for Parent to subclass Employee or vice versa, because it's possible to be one
and not the other.

This is easy to check because it violates the "is a" rule. AnEmployee is not a Parent any more than a
Parent is an Employee.

The "is a" relationship also applies to the instanceof operator. For example, an instance of
Employee is also a Person:

$billg = new Employee;

if ($billg instanceof Person) {

print "Employees are people too!";

Employees are people too!

Since Employee subclasses Person, it's also considered an instance of Person.

Type hinting does an instanceof check, so a type hint for a Person can be either an instance of the
Person class or any class that extends Person, such as Employee.

PHP does not support multiple inheritance, because multiple inheritance introduces many
complexities. For instance, what happens when a class inherits different methods with the same
name? It's not clear which method is the "correct" one. Instead, PHP handles these situations like
Java does: by using interfaces.

As you may remember, an interface is a way to require a class to support a specific set of methods.
When a class is required to implement an interface, you know that you can use the class as you need
without corrupting the object hierarchy. Additionally, PHP 5 allows a class toimplement multiple
interfaces, so you're not constrained to a single class and a single interface.

Sometimes you have a slightly different problem. You have a series of objects that are related using
the "is a" relationship, so it makes logical sense to have them descend from a common parent.
However, while the children are tangible, the parent is abstract.

Take, for example, the Database class used previously in this chapter. A database is a real object, so
it makes sense to have a Database class. However, although Oracle, MySQL, Postgres, MSSQL, and
hundreds of other databases exist, you cannot download and install a generic database. You must
choose a specific database.

2.5.3 Abstract Classes


PHP 5 provides a way for you to create a class that cannot be instantiated. This class is known as an
abstract class. Here's an updated version of the Database class:

abstract class Database {


abstract public function connect( );

abstract public function query( );

abstract public function fetch( );

abstract public function close( );

Mark a class as abstract by placing the abstract keyword before class.

Abstract classes must contain at least one method that is also marked abstract. These methods are
called abstract methods. Database contains four abstract methods: connect( ), query( ), fetch(
), and close( ). These four methods are the basic set of functionality necessary to use a database.

If a class contains an abstract method, the class must also be declared abstract. However, abstract
classes can contain nonabstract methods (even though there are no regular methods inDatabase).

Abstract methods, like methods listed in an interface, are not implemented inside the abstract class.
Instead, abstract methods are implemented in a child class that extends the abstract parent. For
instance, you could use a MySQL class:

class MySQL extends Database {

protected $dbh;

protected $query;

public function connect($server, $username, $password) {

$this->dbh = mysql_connect($server, $username, $password);

public function query($sql) {

$this->query = mysql_query($sql, $this->dbh);

public function fetch( ) {

return mysql_fetch_row($this->query, $this->dbh);


}

public function close( ) {

mysql_close($this->dbh);

If a subclass fails to implement all the abstract methods in the parent class, then it itself is abstract
and another class must come along and further subclass the child. You might do this if you wanted to
create two MySQL classes: one that fetched information as objects and another that returned arrays.

There are two requirements for abstract methods:

Abstract methods cannot be defined private, because they need to be inherited.

Abstract methods cannot be defined final, because they need to be overridden.

PHP 5 offers the ability to restrict child classes from overriding a method in their parent. A final
method is one that cannot be overridden by a child class. This is discussed in more detail inSection
2.5.5, later in this chapter.

2.5.4 Constructors
Inheritance is the reason PHP 5 changed constructor names from the class's name to _ _construct(
). When constructors are tied to class names, it's easy to break code without realizing it. In PHP 4, to
call the parent constructor inside of your class, you must hardcode the parent class name:

// Hardcoded parent constructor name

class MyDatabase extends MySQL {

function MyDatabase( ) {

// automatically populate connection values

parent::MySQL('db.example.com', 'web', 'jsd6w@2d');

}
This class, MyDatabase, is a wrapper for the main MySQL class. The two classes act identically, except
that MyDatabase hardcodes the information necessary to connect to the database.

It uses the parent prefix to explicitly reference the constructor in the parent class. Otherwise, it may
accidentally reference a method named MySQL in the current class. It is unlikely that you'd create
such a method, but this eliminates any potential problems and makes parental reference clear.

However, moving this class under a different parent, or even renaming the parent, forces you to edit
the constructor, replacing the call to parent::MySQL( ) with the new parent constructor's name.

The more flexible solution makes you jump through a few hoops to dynamically determine the name
of the parent's class:

// Dynamic parent constructor name

class MyDatabase extends MySQL {

function MyDatabase( ) {

// automatically populate connection values

$parent = get_parent_class($this);

parent::$parent('db.example.com', 'web', 'jsd6w@2d');

This code is inefficient because you must callget_parent_class( ) every time you create a new
instance of the class.

Using a fixed name for constructors reduces the brittleness of OO code. Calling a parent constructor
is now easy and safe:

// Hardcoded parent constructor name

class MyDatabase extends MySQL {

function MyDatabase( ) {

// automatically populate connection values

parent::_ _construct('db.example.com', 'web', 'jsd6w@2d');

}
With the new naming scheme, you can always use parent::_ _construct( ) and know that it's the
right thing to do.

2.5.5 Final Classes, Methods, and Properties


Inheritance is normally a good thing, but it can make sense to restrict it. The most common reason
to declare a method final is that the method is "perfect." When you believe there's no way to update
the method to make it better, declare it using the final keyword. This prevents subclasses from
ruining it by reimplementing the method in an inferior manner.

Make a method final by placing the final keyword at the beginning of the method declaration:

final public function connect($server, $username, $password) {

$this->dbh = mysql_connect($server, $username, $password);

This prevents someone from subclassing MySQL or creating a different connect( ) method.

To prevent subclassing of an entire class, don't mark each method final. Instead, make a final
class:

final class MySQL extends Database {

A final class cannot be subclassed. This differs from a class in which every method isfinal because
that class can be extended and provided with additional methods, even if you cannot alter any of the
pre-existing methods.

PHP 5 also lets you mark properties as final; however, it does not call them final properties.
Instead, it reuses its concept of constants.

class Math {

const pi = 3.14159; // universal

const e = 2.71828; // constants

$area = math::pi * radius * raidus;


Like static properties, you can access constants without first instantiating a new instance of your
class, and they're accessed using the double colon (::) notation. Prefix the word self:: to the
constant name to use it inside of a class.

Unlike properties, constants do not have a dollar sign ($) before them:

class Circle {

const pi = 3.14159;

protected $radius;

public function _ _construct($radius) {

$this->radius = $radius;

public function diameter( ) {

return 2 * self::pi * $this->radius;

$c = new circle(1);

print $c->diameter( );

6.28318

This example creates a circle with a radius of 1 and then calls the diameter method to calculate its
diameter. To use the class's pi constant, refer to it as self::pi; otherwise, PHP tries to access the
value of the global pi constant:

define('pi', 10); // global pi constant

class Circle {

const pi = 3.14159; // class pi constant

protected $radius;
public function _ _construct($radius) {

$this->radius = $radius;

public function diameter( ) {

return 2 * pi * $this->radius;

$c = new circle(1);

print $c->diameter( );

20

Oops! PHP has used the value of 10 instead of 3.14159, so the new answer is 20 instead of 6.28318.

Although it's unlikely that you will accidentally redefine (you'll probably use the built-inM_PI constant
anyway), this can still slip you up.

You cannot assign the value of an expression to a constant, nor can they use information passed into
your script:

// invalid

class permissions {

const read = 1 << 2;

const write = 1 << 1;

const execute = 1 << 0;

// invalid and insecure


class database {

const debug = $_REQUEST['debug'];

Neither the constants in permissions nor the debug constant in database are acceptable because
they are not fixed. Even the first example, 1 << 2, where PHP does not need to read in external
data, is not allowed.

< Day Day Up >


< Day Day Up >

2.6 Magical Methods


PHP 5 has a few methods that are implicitly invoked behind the scenes. You've already seen two,_
_construct( ) and _ _destruct( ), but these are not the only special methods in PHP 5.

There are seven special methods, and they are as follows:

_ _construct( )

Called when instantiating an object

_ _destruct( )

Called when deleting an object

_ _get( )

Called when reading from a nonexistent property

_ _set( )

Called when writing to a nonexistent property

_ _call( )

Called when invoking a nonexistent method

_ _toString( )

Called when printing an object

_ _clone( )

Called when cloning an object


These methods are easy to spot, since they all begin with two underscores (_ _).

2.6.1 _ _get( ) and _ _set( )


You've read a lot about the benefits of encapsulation and why your classes must have accessor
methods. Writing these methods, however, induces numbness as you repeatedly create methods for
each property in your class. PHP 5 has two special methods, _ _set( ) and _ _get( ), to ease your
pain.

In PHP 4, the only way to handle accessors is to write your own set of methods for each property. By
convention, these methods often begin with the word set and end with the property name. So, to set
a Person's name, you call setName( ).

Using PHP 4 and accessors, the code looks like this:

class Person {

var $name;

var $email;

function setName($name) {

$this->name = $name;

function getName( ) {

return $this->name;

$rasmus = new Person;

$rasmus->setName('Rasmus Lerdorf');

print $ramsus->getName( )

Rasmus Lerdorf
This isn't a problem when there's only a single property, but whenPerson adds email, age, address,
and other details, the class quickly spirals into a long list of almost identical accessors.

PHP 4 requires you to manually write specific accessor functions for every property.[1] That quickly
becomes tiresome as the number of object properties increases, and it makes changing from one
backend to another complicated because you need to edit every single method.
[1]Unless you use the experimental overload extension. If you do, you should know that you can no longer pass
the accessor functions a variable by reference as a final parameter to get( ) or set( ).

Using methods such as getName( ) and setName( ) provides a great degree of protection against
broken code, but forcing people to call getName( ) instead of reading name is clunky. In PHP 4, you
don't have a choice, because that's the only way to implement accessors.

However, since accessors are so important, PHP 5 has two specific methods that implement them:_
_get( ) and _ _set( ). The _ _get( ) method automatically intercepts access to any undefined
property reads. Likewise, _ _set( ) does the same for property writes.

Example 2-3 reimplements the class in PHP 5 using _ _get( ) and _ _set( ).

Example 2-3. Implementing magic accessor methods

class Person {

private $data;

public function _ _get($property) {

if (isset($this->data[$property])) {

return $this->data[$property];

} else {

return false;

public function _ _set($property, $value) {

$this->data[$property] = $value;

}
$rasmus = new Person;

$rasmus->name = 'Rasmus Lerdorf';

print $rasmus->name;

Rasmus Lerdorf

With _ _get( ) and _ _set( ), you can use what appear to be public properties, such as $rasmus-
>name, without violating encapsulation. This is because the programmer isn't reading from and
writing to those properties directly, but is instead being routed through accessor methods.

The _ _get( ) method takes the property name as its single parameter. Within the method, you
check to see whether that property has a value inside $data. If it does, the method returns that
value; otherwise, it returns false.

This lets you continue accessing properties using the familiar property syntax, even though the actual
data is someplace else.

When you read $rasmus->name, you actually call _ _get('name') and it's
returning $data['name'], but for all external purposes that's irrelevant.

The _ _set( ) method takes two arguments: the property name and the new value. Otherwise, the
logic inside the method is similar to _ _get( ).

There are two downsides to using _ _get( ) and _ _set( ). First, these methods only catch
missing properties. If you define a property for your class, _ _get( ) and _ _set( ) are not
invoked by PHP.

This is the case even if the property you're trying to access isn't visible in the current scope (for
instance, when you're reading a property that exists in the class but isn't accessible to you, because
it's declared private). Doing this causes PHP to emit a fatal error:

PHP Fatal error: Cannot access private property...

Second, these methods completely destroy any notion of property inheritance. If a parent object has
a _ _get( ) method and you implement your own version of _ _get( ) in the child, your object
won't function correctly, because the parent's _ _get( ) method is never called.

2.6.2 _ _call( )
PHP 5 also has a _ _call( ) method. It captures any calls to undefined methods in a class. The_
_call( ) method is useful for implementing an OO concept known as aggregation.
With aggregation, one object acts as a container for one or more additional objects. This is another
way of solving the problem of multiple inheritance because you can easily piece together an object
out of smaller components.

For example, a Person object can contain an Address object. Clearly, People have addresses.
However, addresses aren't unique to people; they also belong to businesses and other entities.
Therefore, instead of hardcoding address information inside of Person, it makes sense to create a
separate Address class that can be used by multiple classes.

Example 2-4 shows how this works in practice.

Example 2-4. Aggregating an Address object

class Address {

protected $city;

protected $country;

public function setCity($city) {

$this->city = $city;

public function getCity( ) {

return $this->city;

public function setCountry($country) {

$this->country = $country;

public function getCountry( ) {

return $this->country;

}
}

class Person {

protected $name;

protected $address;

public function _ _construct( ) {

$this->address = new Address;

public function setName($name) {

$this->name = $name;

public function getName( ) {

return $this->name;

public function _ _call($method, $arguments) {

if (method_exists($this->address, $method)) {

return call_user_func_array(

array($this->address, $method), $arguments);

The Address class is set up just like the Person class you have seen before. However, it stores a
city and country instead of a name.

Person has setName( ) and getName( ), like before, but it also has two new methods: _
_construct( ) and _ _call( ).

Its constructor instantiates an Address object and stores it in a protected $address property. This
allows methods inside Person to access $address, but prevents others from talking directly to the
class.

Ideally, when you call a method that exists in Address, PHP would automatically execute it. This does
not occur, since Person does not extend Address. You must write code to glue these calls to the
appropriate methods yourself.

Wrapper methods are one option. For example:

public function setCity($city) {

$this->address->setCity($city);

This setCity( ) method passes along its data to the setCity( ) method stored in $address. This is
simple, but it is also tedious because you must write a wrapper for every method.

Using _ _call( ) lets you automate this process by centralizing these methods into a single place:

public function _ _call($method, $arguments) {

if (method_exists($this->address, $method)) {

return call_user_func_array(

array($this->address, $method), $arguments);

The _ _call( ) method is invoked with two arguments: the name of the method and an array
holding the parameters passed to the method. The first argument lets you see which method was
called, so you can determine whether it's appropriate to dispatch it to$address.

Here, you want to pass along the method if it's a valid method of theAddress class. Check this using
method_exists( ), providing the object as the first parameter and the method name as the second.

If the function returns true, you know this method is valid, so you can call it. Unfortunately, you're
still left with the burden of unwrapping the arguments out of the$arguments array. That can be
painful.

The seldom used and oddly named call_user_func_array( ) function solves this problem. This
function lets you call a user function and pass along arguments in an array. Its first parameter is
your function name, and the second is the array of arguments.

In this case, however, you want to call an object method instead of a function. There's a special
syntax to cover this situation. Instead of passing the function name, you pass an array with two
elements. The first element is the object, and the other is the method name.

This causes call_user_func_array( ) to invoke the method on your object. You must then return
the result of call_user_func_array( ) back to the original caller, or your return values will be
silently discarded.

Here's an example of Person that calls both a method defined in Person and one from Address:

$rasmus = new Person;

$rasmus->setName('Rasmus Lerdorf');

$rasmus->setCity('Sunnyvale');

print $rasmus->getName( ) . ' lives in ' . $rasmus->getCity( ) . '.';

Rasmus Lerdorf lives in Sunnyvale.

Even though setCity( ) and getCity( ) aren't methods of Person, you have aggregated them into
that class.

You can aggregate additional objects into a single class, and also be more selective as to which
methods you expose to the outside user. This requires some basic filtering based on the method
name.

When deciding if aggregation is the right technique, ask yourself if the "has a" rule applies. Here a
Person has an Address, so it makes sense to aggregate. However, keep in mind that aggregate
relationships are weaker than inheritance relationships because they're not as intricately bound to the
object's identity.

2.6.3 _ _toString( )
PHP 5 provides objects with a way to control how they are converted to strings. This allows you to
print an object in a friendly way without resorting to lots of additional code.

PHP 4 requires you to call a method that returns a string. For example:

class Person {

var $name;

var $email;
function setName($name) {

$this->name = $name;

function setEmail($email) {

$this->email = $email;

function toString( ) {

return "$this->name <$this->email>";

$rasmus = new Person;

$rasmus->setName('Rasmus Lerdorf');

$rasmus->setEmail('[email protected]');

print $rasmus->toString( );

Rasmus Lerdorf <[email protected]>

This is effective, but there's no requirement that the string conversion method is alwaystoString(
), so you can't rely on it from class to class.

Also, it adds visual clutter to your code because you're constantly writing->toString( ) whenever
you want to print an object. PHP should know that when you try to print an object, you want a
special string representation of the contents of the object.

PHP 5 introduces a special method that lets you to control how PHP displays objects. PHP calls an
object's _ _toString( ) method when you echo or print the object by itself.

The PHP 5 Person class is identical to the PHP 4 version, except that toString( ) has been renamed
_ _toString( ):

class Person {

protected $name;

protected $email;

public function setName($name) {

$this->name = $name;

public function setEmail($email) {

$this->email = $email;

public function _ _toString( ) {

return "$this->name <$this->email>";

Now, instead of writing:

$rasmus = new Person;

$rasmus->setName('Rasmus Lerdorf');

$rasmus->setEmail('[email protected]');

print $rasmus->toString( );

you can write:

$rasmus = new Person;

$rasmus->setName('Rasmus Lerdorf');
$rasmus->setEmail('[email protected]');

print $rasmus;

Rasmus Lerdorf <[email protected]>

This feature does not work for interpolated or concatenated strings:

print "PHP was created by $rasmus";

print 'PHP was created by '. $rasmus;

printf('PHP was created by %s', $rasmus);

PHP was created by Object id #1

PHP was created by Object id #1

PHP was created by Object id #1

The one exception is a dusty corner of PHP that uses echo and a comma (,) instead of period (.) to
combine items:

echo 'PHP was created by ', $rasmus;

PHP was created by Rasmus Lerdorf <[email protected]>

PHP 5 will not autoconvert objects to strings when you pass them to a function that requires a string
argument. You should call _ _toString( ) on them instead:

print htmlentities($rasmus); // bad

print htmlentities($rasmus->_ _toString( )); // good

While such limited support for _ _toString( ) reduces its usefulness, future versions of PHP should
extend this behavior to also call _ _toString( ) when you:

Place the object inside double quotes or a heredoc

Concatenate with the object using dot (.)

Cast the object to a string using (string) or strval( )

Treat the object as a string in printf( ) by indicating it should be formatted with %s

Therefore, it's best to start using the _ _toString( ) naming convention when you transition to PHP
5 because then there will be less code to modify when you want to take advantage of the newer
features.
2.6.4 _ _clone( )
Since PHP 5 copies objects by reference instead of value, you need to useclone to duplicate the
contents of an object. Otherwise, the second object is simply a reference to the first.

This cloning process copies every property in the first object to the second. This includes properties
holding objects, so the cloned object may end up sharing object references with the original.

This is frequently not the desired behavior. For example, consider the aggregated version ofPerson
that holds an Address object, shown earlier in Example 2-4. The key point to remember is that the
$address property holds an Address object.

With this class, here's what happens when you clone an object:

$rasmus = new Person;

$rasmus->setName('Rasmus Lerdorf');

$rasmus->setCity('Sunnyvale');

$zeev = clone $rasmus;

$zeev->setName('Zeev Suraski');

$zeev->setCity('Tel Aviv');

print $rasmus->getName( ) . ' lives in ' . $rasmus->getCity( ) . '.';

print $zeev->getName( ) . ' lives in ' . $zeev->getCity( ) . '.';

Rasmus Lerdorf lives in Tel Aviv.

Zeev Suraski lives in Tel Aviv.

Interesting. Calling setName( ) worked correctly because the $name property is a string, so it's
copied by value. However, since $address is an object, it's copied by reference, so getCity( )
doesn't produce the correct results, and you end up relocating Rasmus to Tel Aviv.

This type of object cloning is known as a shallow clone or a shallow copy because PHP does not
clone objects held in properties. In contrast, a deep clone occurs when all objects involved are
cloned. This is PHP 4's cloning method.

Control how PHP 5 clones an object by implementing a _ _clone( ) method in your class. When this
method exists, PHP allows _ _clone( ) to override its default behavior:
class Person {

// ... everything from before

public function _ _clone( ) {

$this->address = clone $this->address;

Inside of _ _clone( ), you're automatically presented with a shallow copy of the variable, stored in
$this, the object that PHP provides when _ _clone( ) does not exist.

Since PHP has already copied all the properties, you only need to overwrite the ones you dislike.
Here, $name is okay, but $address needs to be explicitly cloned.

Now the clone behaves correctly:

$rasmus = new Person;

$rasmus->setName('Rasmus Lerdorf');

$rasmus->setCity('Sunnyvale');

$zeev = clone $rasmus;

$zeev->setName('Zeev Suraski');

$zeev->setCity('Tel Aviv');

print $rasmus->getName( ) . ' lives in ' . $rasmus->getCity( ) . '.';

print $zeev->getName( ) . ' lives in ' . $zeev->getCity( ) . '.';

Rasmus Lerdorf lives in Sunnyvale.

Zeev Suraski lives in Tel Aviv.

Using the clone operator on objects stored in properties causes PHP to check whether any of those
objects contain a _ _clone( ) method. If one exists, PHP calls it. This repeats for any objects that
are nested even further.

This process correctly clones the entire object and demonstrates why it's called a deep copy.

< Day Day Up >


< Day Day Up >

Chapter 3. MySQL
PHP 5 sports a new MySQL extension. While this extension is similar in spirit to the original MySQL
extension, it has many new features and other improvements. To differentiate it from the original
version, this extension is called the "Improved MySQL extension," or mysqli for short.

The changes to mysqli come from two places. The majority are from new features available in
MySQL 4.1. However, PHP 5 also allows mysqli to add an object-oriented interface.

Here's a list of the major advances in mysqli:

Compatibility with MySQL 4.1 and above

Prepared statements and bound parameters

Object-oriented interface

Secure connections using SSL

Additionally, MySQL 4.1 has new SQL-level capabilities that you can use from PHP. They include:

Subselects

Transactions

Fulltext searching

Unicode support

Geospacial support (GIS)

On the downside, there are a few wrinkles if you want to start usingmysqli with your existing PHP
projects:

Its client libraries are not bundled with PHP 5.

It does not work with MySQL 4.0 and below.

It's missing some mysql functions.

In a nutshell, if all your projects use MySQL 4.1 and later, and you have no legacy MySQL code, then
all you need to do is download the MySQL client libraries and start usingmysqli. On the other hand,
you'll have some work to do if you:
Want to use MySQL 4.1, but have existing mysql-based code

Must write code that runs under MySQL 4.0 (and below) and 4.1 (and above)

Want to have some applications use a MySQL 4.0 database and others use a MySQL 4.1
database

The Section 3.10 at the end of this chapter discusses a few strategies for handling these types of
situations. However, before discussing how to move from mysql to mysqli, this chapter covers why
you'd want to make the switch.

The chapter begins by showing how to use the mysqli extension, with sections on both the
procedural and object-oriented interfaces. Next comes prepared statements and bound parameters.
These are database features that require a different set of functions to communicate with MySQL
than before, but offer improved speed and ease-of-use.

The middle of the chapter covers subselects and transactions. These are newMySQL 4.1 features
that aren't unique to mysqli and PHP, but are part of the MySQL 4.1 server. Subselects let you nest
a query inside of another, and transactions allow you to group a series of SQL queries as an
integrated unit. Queries wrapped inside a transaction are guaranteed to either all work or all fail.

MySQL 4.1 allows you to group multiple queries into a single request. This is useful when you're using
PHP to restore a database or need to populate a database from scratch. The section Section 3.8
covers the new interface for processing the results of a multi-query, which is more complicated than
a regular query.

Another MySQL 4.1 feature is the ability to encrypt the connection between PHP and MySQL using
Secure Sockets Layer (SSL). The next section shows how to configure PHP and MySQL to support SSL
connections and how to use mysqli to pass authentication credentials.

At the end of the chapter, there's a discussion of various migration strategies for moving your code to
both mysqli and MySQL 4.1. Due to various incompatibilities, this process requires a careful plan of
attack.

While this chapter covers many MySQL topics, it omits some advanced topics, such query
optimization, backups, and replication. For more information on those topics,read High Performance
MySQL, by Jeremy D. Zawodny and Derek J. Balling (O'Reilly). That book covers those three issues
and more, including a complete chapter on securing MySQL from crackers.

< Day Day Up >


< Day Day Up >

3.1 Installing and Configuring


You must download and install MySQL 4.1 or greater to use mysqli. In particular, if you use the
MySQL library bundled with PHP 4, you now must download and install a new version of MySQL.

While PHP 4 bundles a copy of the MySQL client libraries, PHP 5 does not. As of MySQL 4.1, the entire
MySQL application is licensed under the GNU General Public License (GPL). Since PHP uses a different
type of open source license, PHP was legally unable to bundle the libraries without modifying their
license.

MySQL eventually added a special license exemption for many free and open source products,
including PHP. However, the PHP Group decided not to restore the libraries. Since many people
already had the libraries on their systems, bundling the libraries actually caused conflicts when the
bundled library differed from the preinstalled version.

As a result, if you don't already have a copy of the MySQL client libraries, you need to install one. You
can download MySQL from http://www.mysql.com/. If you're using mysqli, be sure to use a copy of
MySQL 4.1.2 or greater, as earlier versions won't work with PHP.

Before installing MySQL 4.1, you should read the Section 3.10. You may have migration problems,
particularly if you're upgrading directly from MySQL 3.2.x and skipping MySQL 4.0. Reading this
section isn't necessary if MySQL 4.1 is your first version of MySQL.

After you've installed MySQL, you can enable the mysqli extension with the --with-mysqli flag
during the PHP configure process.

Unlike for mysql, you don't tell PHP where MySQL is located by adding the path to the MySQL base
installation. Instead, PHP uses MySQL's new mysql_config file.

Here's an example of the difference between the configuration options formysql and mysqli for a
MySQL installation in /usr/local/mysql:

// mysql

--with-mysql=/usr/local/mysql

// mysqli

--with-mysqli=/usr/local/mysql/bin/mysql_config

You can enable both mysql and mysqli at the same time, which is useful when you need to support
legacy applications. You should link PHP against the same set of MySQL libraries to prevent compiler
errors.
< Day Day Up >
< Day Day Up >

3.2 Procedural Interface


The procedural interface to mysqli is largely identical to the older mysql extension. Except, of
course, all the function names begin with mysqli instead of mysql:

$db = mysqli_connect($server, $user, $password) or

die("Could not connect: " . mysqli_error( ));

mysqli_select_db($db, "users");

$result = mysqli_query($db, "SELECT username FROM users");

while ($row = mysqli_fetch_assoc($result)) {

print $row['username'] . "\n";

mysqli_free_result($result);

mysqli_close($db);

This code connects to a MySQL database, selects the users table, makes a query, and then fetches
each row as an associative array. These functions all behave the same as their mysql counterparts,
except that the mysqli functions require a database handle as their first argument instead of
optionally allowing one as the final argument. Section 3.10 covers all the API changes in detail.

There is also a minor change in mysqli_fetch_array( ). When there are no additional rows, it
returns NULL. The original extension returns false. This difference won't affect code like that shown
here, where it only assigns the result to a variable, but if you use!= = to do a strict check against
false, you must now check against NULL.

3.2.1 Alternative Fetch Methods


If you prefer different MySQL fetch methods, they're also inmysqli. Given the same query of SELECT
username FROM users, these example functions all print the same results:
// Fetch numeric arrays:

while ($row = mysqli_fetch_row($result)) {

print $row[0] . "\n";

// Alternative syntax:

while ($row = mysqli_fetch_array($result, MYSQLI_NUM)) {

print $row[0] . "\n";

// Alternative associative array syntax:

while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {

print $row['username'] . "\n";

// Both numeric and associative:

while ($row = mysqli_fetch_array($result, MYSQLI_BOTH)) {

print $row[0] . "\n";

print $row['username'] . "\n";

// Fetch as "object"

while ($row = mysqli_fetch_object($result)) {

print $row->username . "\n";

}
< Day Day Up >
< Day Day Up >

3.3 Before and After: Connecting to the Database Server


Before you can issue queries, you need to connect to the MySQL server. This sounds so basic that
you'd think it would be the same in both versions of the MySQL extension, but there are quite a few
changes.

Connecting isn't just specifying the location of your database and providing a username and
password. You also specify a variety of configuration options, such as whether to use SSL and the
number of seconds before the connection times out.

3.3.1 mysql: Making a Connection


The mysql connection functions take five parameters:

mysql_connect(server, username, password, new_link, client_flags)

All of these parameters are actually optional, because the extension defaults to values specified in a
series of MySQL-related configuration directives, such as mysql.default_host.

The server parameter is usually the same as the host, but you can also append a port name or a
path to a socket. For example, if your database runs on port 3307 on db.example.org:

mysql_connect('db.example.org:3307');

Separate the hostname and port with a colon (:) so PHP can tell them apart.

The username and password variables are not the username and password for your local account,
but for MySQL's account system.

By default, if you try to reconnect to the same database with the same set of credentials, PHP will
reuse the existing connection. Setting new_link to true forces PHP to always make another link to
MySQL.

Use the final parameter, client_flags, to control the session. You can modify it by passing any
combination of the following three constants: MYSQL_CLIENT_COMPRESS,
MYSQL_CLIENT_IGNORE_SPACE, and MYSQL_CLIENT_INTERACTIVE. These tell MySQL to compress the
connection, ignore spaces after functions, and modify how it determines when to close the
connection, respectively.

3.3.2 mysqli: Making a Connection


Under mysqli there are two different ways to connect to MySQL: the familiar mysqli_connect( )
and a new method, mysqli_real_connect( ).

Don't feel that mysqli_real_connect( ) is somehow superior to mysqli_connect( ) because it


contains the word real. It's not. This name comes simply from the underlying MySQL C API. Besides,
mysqli_connect( ) uses the same C functions as mysqli_real_connect( ); it just wraps them in
an easy-to-use form.

Unfortunately, while mysqli_connect( ) looks similar to mysql_connect( ), it is not identical. For


one, it now takes six parameters, all of them still optional:

mysqli_connect(hostname, username, password, database, port, socket)

The first three options-hostname, username, and password-are the same as mysql_connect( ),
but the remaining three are different. The database parameter controls which database you want to
query. Using this parameter is the same as calling mysql_select_database( ). This function is still
available as mysqli_select_database( ), but you should not need to use it unless you are reusing
the same connection to switch from one database to another on the same server.

The port option controls which port to contact on the database server, and socket specifies which
socket to use. This is identical to adding :port or :/path/to/socket after the hostname in
mysql_connect( ). (You cannot use this syntax in mysqli.) These two parameters are mutually
exclusive. If you use one, there's no purpose in using the other.

Here's an example that uses the first five options:

mysqli_connect('db.example.org', 'web', 'xyz@123', 'users', 3306);

This function tries to connect to a MySQL server running on db.example.com on port 3306. It uses a
MySQL username of web with password xyz@123. If it's authorized, it then sets the database to
events, just like entering USE events; in the MySQL prompt.

The mysql_pconnect( ) function has been removed. You cannot create persistent connections using
MySQLi. While the goal of persistent connections-reducing the overhead time necessary to establish
a connection-was a good idea, they caused problems. On high traffic sites they actually prevented
PHP from accessing MySQL because they were holding onto available connections.

The mysql extension had an optional client_ flags parameter, which let you set options such as
whether the connection should compress data. These options now require a different set of
connection functions:

$db = mysqli_init( );

mysqli_real_connect($db, 'db.example.com', 'web', 'xyz@123', 'users',

3306, NULL, MYSQLI_CLIENT_COMPRESS);

The mysqli_init( ) function returns a MySQL object that can then be used by
mysqli_real_connect( ) to make the connection.
The mysqli_real_connect( ) function has the same syntax as mysqli_connect( ), except that
you can add client flags as a seventh parameter. The call just shown is identical to the example using
mysqli_connect( ), except that it also compresses the connection.

Table 3-1 shows the five constants you can pass to mysqli_real_connect( ). It includes the three
flags available with mysql, but also adds two new ones: MYSQLI_CLIENT_FOUND_ROWS and
MYSQLI_CLIENT_SSL. The pre-existing constants have also been renamed to begin withMYSQLI.

Table 3-1. mysqli_real_connect( ) options

Name Description
MYSQLI_CLIENT_COMPRESS Compress the connection.

Return the number of found rows instead of the number of


MYSQLI_CLIENT_FOUND_ROWS
changed rows.
MYSQLI_CLIENT_IGNORE_SPACE Allow spaces after function names.

Close the connection after interactive_timeout seconds instead


MYSQLI_CLIENT_INTERACTIVE of wait_timeout seconds. These values are usually the same
unless you've modified your MySQL configuration.

Encrypt the connection using SSL (should be automatically invoked


MYSQLI_CLIENT_SSL
when you add SSL information).

For more information on these options, see http://www.mysql.com/doc/en/mysql_options.html.

Using mysqli_init( ) also allows you to set a variety of other configuration options before
establishing the link:

$db = mysqli_init( );

mysqli_options($db, MYSQLI_OPT_CONNECT_TIMEOUT, 120);

mysqli_options($db, MYSQLI_OPT_LOCAL_INFILE, false);

mysqli_real_connect($db, 'db.example.com', 'web', 'xyz@123', 'users',

3306, NULL, MYSQLI_CLIENT_COMPRESS);

The mysqli_options( ) function provides you with the ability to control more esoteric details about
the connection. Since these options usually allow you to specify a parameter, they can't be passed
alongside the other client flags.

This example sets the connection timeout to 120 seconds and disables the ability to useLOAD LOCAL
INFILE. See Table 3-2 for a list of all the connection options usable in mysqli_options( ) and
mysqli_real_connect( ).

Table 3-2. mysqli_options( ) options

Name Description
MYSQLI_READ_DEFAULT_GROUP Read the options for this group from your MySQL configuration file.
MYSQLI_READ_DEFAULT_FILE Use this file as your MySQL configuration file instead ofmy.cnf.
MYSQLI_OPT_CONNECT_TIMEOUT Number of seconds before MySQL times out.

MYSQLI_OPT_LOCAL_INFILE Enable the LOAD LOCAL INFILE command.

Always issue this command when connecting (and reconnecting) to


MYSQLI_INIT_COMMAND
the server.

Additional details on these options are available at


http://www.mysql.com/doc/en/mysql_options.html.

< Day Day Up >


< Day Day Up >

3.4 Object-Oriented Interface


The mysqli extension also allows you to manipulate the data using an object-oriented interface.
Everything that can be done using the procedural interface is available this way. Here is the same
sample transaction from the last section translated into the OO style:

$mysqli = new mysqli('db.example.org', 'web', 'xyz@123');

$mysqli->select_db('users');

$result = $mysqli->query("SELECT username FROM users");

while ($row = $result->fetch_assoc( )) {

print $row['username'] . "\n";

$result->close( );

With the OO interface, there's no need to pass database handles as the first parameter to amysqli
method. Instead, the extension stores that handle as part of the mysqli object.

If you're porting code from mysql and use the default link option, you may find it easier to switch to
the OO syntax because it doesn't require you to modify the argument list of everymysql function.

You can also use mysqli_init( ) and mysqli_real_connect( ):

$mysqli = new mysqli( );

$mysqli->init( );

$mysqli->options(MYSQLI_OPT_CONNECT_TIMEOUT, 120);

$mysqli->options(MYSQLI_OPT_LOCAL_INFILE, false);

$mysqli->real_connect('db.example.com', 'web', 'xyz@123', 'users',


3306, NULL, MYSQLI_CLIENT_COMPRESS);

A new mysqli object does not automatically call mysqli_init( ) in its constructor. This is still your
responsibility.

Certain mysqli functions are object properties instead of methods, in particular, mysqli_error( ),
mysqli_errno( ), and mysqli_insert_id( ). Here's an example using mysqli_insert_id( ):

// place new e-mail address on list:

$email = '[email protected]';

$list = 'php-general';

// escape data

$email = $mysqli->real_escape_string($email);

$list = $mysqli->real_escape_string($list);

$mysqli = new mysqli($server, $user, $password);

$mysqli->query("INSERT INTO addresses VALUES(NULL, '$email')");

$id = $mysqli->insert_id; // no ( ) necessary!

$mysqli->query("INSERT INTO lists VALUES($id, '$list')");

A complete list of MySQLi classes, methods, and properties can be found at


http://www.php.net/mysqli.

< Day Day Up >


< Day Day Up >

3.5 Before and After: Querying and Retrieving Data with


Prepared Statements
One way to speed up MySQL is to use prepared statements. Every time you make an SQL query,
MySQL parses the request, checks its validity, and executes it. While MySQL does this efficiently,
prepared statements make this process even faster.

A prepared statement is a way to tell MySQL what your query will look like before you actually
execute it. This description doesn't contain the exact query; it's more like a query template. Most of
the query is hardcoded, but there are placeholders where you want to customize the information.

You pass this template to MySQL. It parses and validates the query, and returns a statement handle.
You then use that handle to execute the request.

In contrast to a traditional query, when you execute this type of request, there's no need to parse
the SQL. As a result, MySQL executes the query faster. If you make the same query more than once,
it's faster to use a prepared statement than a direct query. Additionally, prepared statements
automatically escape quotes, so you don't need to worry about stray characters. This is a big benefit
that makes prepared statements worthwhile even for single queries.

Prepared statements also let you control data retrieval. There's no longer a need to first retrieve a
row into an array and then assign each element to an individual variable. You can instruct MySQL to
place each piece of data directly into a variable.

3.5.1 PHP 4: Traditional Input Queries


Example 3-1 demonstrates the traditional way to insert data into a table.

Example 3-1. Executing a traditional query

// User-entered data

$username = 'rasmus';

$password = 'z.8cMpdFbNAPw';

$zipcode = 94088;

// Escape data

$username = mysqli_real_escape_string($db, $username);


$password = mysqli_real_escape_string($db, $password);

$zipcode = intval($zipcode);

// Create SQL query

$sql = "INSERT INTO users VALUES ('$username', '$password', $zipcode)";

// Make SQL query

mysqli_query($db, $sql) or die('Error');

There are three distinct parts of the process: escaping the data, creating the query, and executing
the query.

It's vital not to omit the three lines at the top. When you insert user-entered information into a
database, you must prevent the possibility that the user can create an invalid query or even
maliciously alter the query. The mysqli_real_escape_string( ) function ensures strings are
treated as data by escaping single quotes, and the intval( ) function converts the data to an
integer, stripping away non-numbers in the process. This prevents MySQL from interpreting them as
part of the query.

The mysqli_real_escape_string( ) function differs from mysql_escape_string( ) because


mysqli_real_escape_string( ) respects the character encoding of your data. The older
mysql_escape_string( ) is obsolete and is not available under mysqli.

After escaping the data, you take the fields and create an SQL statement. Although you can build the
SQL directly inside mysqli_query( ), assigning it to a variable such as $sql makes debugging easier
because you can easily do a print $sql (or another form of logging) to discover if what you're
actually passing MySQL is what you think you're passing.

When the SQL's complete, it's passed to MySQL using mysqli_query( ). MySQL then receives the
data, parses it, reports errors if they exist, executes the query, and returns a result.

This is standard stuff to most PHP programmers. You can continue to use this method in PHP 5 under
mysqli, but as you'll see, there's a better method.

3.5.2 PHP 5: Binding Input Parameters


MySQL 4.1 introduces a new way to query a database. With this approach, you create a statement
with input parameter placeholders. This template is then filled in with data when you execute the
query.

Example 3-2 is a revised version of Example 3-1.


Example 3-2. Querying with bound input parameters

// User-entered data

$username = 'rasmus';

$password = 'z.8cMpdFbNAPw';

$zipcode = 94088;

// Prepare statement

$sql = 'INSERT INTO users VALUES(?, ?, ?)';

$stmt = mysqli_stmt_init($db);

if (mysqli_stmt_prepare($stmt, $sql)) {

// Associate placeholders with data type and variable name

// 'ssi' tells MySQL you're passing two strings and an integer

mysqli_stmt_bind_param($stmt, 'ssi', $username, $password, $zipcode);

// Executing statement

mysqli_stmt_execute($stmt);

This is a big twist. There are question marks (?) in the SQL where your variables used to be, and the
calls to mysql_real_escape_string( ) and intval( ) have disappeared.

The new method separates querying a database into three parts. Before, you passed MySQL the SQL
with the data and told MySQL to execute it in one step. Here, you first tell MySQL the query without
any data. This is known as preparing the query. Next, you tell MySQL what types of information are
going in each spot in the query and which PHP variables hold the data. This is calledbinding
parameters. Finally, you pass along the data and execute the query. This is calledexecuting the
query.

When you prepare the query, you create an SQL statement that looks identical to what you normally
write, but you substitute a question mark for each variable. Here's a before and after example:

// Before
$sql = "INSERT INTO events VALUES ('$name', '$password', $zipcode)";

// After

$sql = 'INSERT INTO events VALUES(?, ?, ?)';

The new SQL statement is like a template that's going to be filled in later on, and the question marks
tell MySQL where to place the data. Notice how there's no need to place single quotation marks
around the first and second parameters. When you tell MySQL a parameter is a string, MySQL
automatically quotes the variable for you. Parameter type identification is explained shortly.

The next step is to create a new MySQL statement. First, initialize a blank statement handle with
mysqli_stmt_init( ). Pass this and your SQL to mysqli_stmt_prepare( ). This sends your
statement to MySQL, where the server can check whether your syntax is correct. This function
returns true if everything is okay and false if there's a problem.

if (mysqli_stmt_prepare($stmt, $sql)) {

All future interaction with the query goes through the statement handle,$stmt, instead of the
database handle, $db.

Now that the statement is prepared, bind the parameters using mysqli_stmt_bind_param( ):

// Associate placeholders with data type and variable name

mysqli_stmt_bind_param($stmt, 'ssi', $username, $password, $zipcode);

This function takes a variable number of arguments. The first parameter is the statement handle. The
second is a string that describes each SQL placeholder. If you want MySQL to treat the variable as
text, use s; for integers, i; for all other numbers, d; and for blobs, b. In this example, the first two
placeholders are strings and the last one is an integer, so the argument isssi.

The remaining arguments are the variables that you want PHP to pass to MySQL when the query is
executed. This is different from passing the variables themselves, as in a traditional function call.
MySQL does not take what's currently stored in the variables; instead, it takes what will be stored in
the variables at the time of the query's execution. This is a big difference.

In this case, the end result would be the same either way because you don't alter the values of
$username and the other two variables in between binding them and executing the query, but later
on you see an example where the distinction is crucial.

You must have an equal number of question marks in your statement, characters in the placeholder
types parameter, and arguments following that parameter. (Here there are three of each.) Failing to
do so causes MySQL to return an error.

Finally, you instruct MySQL to execute the statement:

// Executing statement
mysqli_stmt_execute($stmt);

Since you've already prepared the SQL and bound the variables, MySQL needs no new information,
so mysqli_stmt_execute( ) is the least complicated part of the process.

You can also use prepared statements with mysqli's OO interface. This example does the same thing
as Example 3-2:

// User-entered data

$username = 'rasmus';

$password = 'z.8cMpdFbNAPw';

$zipcode = 94088;

// Prepare statement

$sql = 'INSERT INTO users VALUES(?, ?, ?)';

$stmt = $db->stmt_init( );

if ($stmt->prepare($sql)) {

// Associate placeholders with data type and variable name

// 'ssi' tells MySQL you're passing two strings and an integer

$stmt->bind_param('ssi', $username, $password, $zipcode);

// Executing statement

$stmt->execute( );

With the OO API, substitute mysqli_stmt_init($db) with $db->stmt_init( ). This method returns
a statement object, upon which you can invoke prepare( ), bind_param( ), and execute( )
methods.

Bound input and output parameters are associated with the specific variable, not the variable name.
This matters when you call mysqli_stmt_bind_param( ) in a different scope than
mysqli_stmt_execute( ). For instance, this works:
function execute_my_statement($stmt) {

// Execute prepared statement

mysqli_stmt_execute($stmt);

// Varibles defined in main scope

$username = 'rasmus';

$password = 'z.8cMpdFbNAPw';

$zipcode = 94088;

// Prepare statement

$sql = 'INSERT INTO users VALUES(?, ?, ?)';

$stmt = mysqli_stmt_init($db);

if (mysqli_stmt_prepare($stmt, $sql)) {

// Associate placeholders with data type and variable name

// 'ssi' tells MySQL you're passing two strings and an integer

mysqli_stmt_bind_param($stmt, 'ssi', $username, $password, $zipcode);

execute_my_statement($stmt);

Even though you're calling mysqli_stmt_execute( ) inside execute_my_statement( ), mysqli


contains a reference to $username from the main scope. This allows the query to execute properly.

However, this also means the following won't work:

function execute_my_statement($stmt) {
// Varibles defined in local scope

$username = 'rasmus';

$password = 'z.8cMpdFbNAPw';

$zipcode = 94088;

// Execute prepared statement

mysqli_stmt_execute($stmt);

// Prepare statement

$sql = 'INSERT INTO users VALUES(?, ?, ?)';

$stmt = mysqli_stmt_init($db);

if (mysqli_stmt_prepare($stmt, $sql)) {

// Associate placeholders with data type and variable name

// 'ssi' tells MySQL you're passing two strings and an integer

mysqli_stmt_bind_param($stmt, 'ssi', $username, $password, $zipcode);

execute_my_statement($stmt);

In this example, you're binding $username in the main scope, but setting a different $username
inside execute_my_statement( ). Just because both variables have the same
n a m e -$username-doesn't mean they're references to each other.

3.5.3 PHP 5: Binding Input Parameters with Multiple Queries


The previous example inserted only one piece of data into the database. Where prepared statements
really shine, however, is when you need to execute the same query multiple times. In these cases,
there's no need to reprepare or rebind the query. You can just load new information into the bound
variables and execute the statement another time. Example 3-3 shows how to enter multiple users
into the database.

Example 3-3. Querying with bound input parameters multiple times

$users = array(array('rasmus', 'z.8cMpdFbNAPw', 94088),

array('zeev' , 'asd34.23NNDeq', 0));

// Prepare statement

$sql = 'INSERT INTO users VALUES(?, ?, ?)';

$stmt = mysqli_stmt_init($db);

if (mysqli_stmt_prepare($stmt, $sql)) {

// Associate placeholders with data type and variable name

// 'ssi' tells MySQL you're passing two strings and an integer

mysqli_stmt_bind_param($stmt, 'ssi', $username, $password, $zipcode);

foreach ($users as $user) {

// Place data into bound variables

list($username, $password, $zipcode) = $user;

// Execute prepared statement

mysqli_stmt_execute($stmt);

The initial setup in Example 3-3 is identical to previous examples, but instead of executing the
statement once, mysqli_stmt_execute( ) lives inside a foreach loop. Every time the loop iterates,
new values are placed in $username, $password, and $zipcode, and mysqli_stmt_execute( )
makes a new query. Even though mysqli_stmt_execute($stmt) hasn't changed, the query is
different because the underlying data in the bound parameters has been updated.

This is where you can truly appreciate the benefit of using bound parameters. Not only is it
significantly easier to re-execute a query using PHP, MySQL also executes them faster because it's
already prepared the statement.

At present, you cannot bind array elements. It is tempting to omit the call tolist and do the
following:

mysqli_stmt_bind_param($stmt, 'ssi', $user[0], $user[1], $user[2]);

foreach ($users as $user) {

// Execute prepared statement

mysqli_stmt_execute($stmt);

This does not work. MySQLi will not grab elements from $user.

3.5.4 PHP 5: Binding Output Parameters


You can use bound parameters for more than just placing data into MySQL. You can also use them to
retrieve data from MySQL.

Under the old MySQL extension, when you retrieved a row or column from the database, you needed
to assign the result to a variable. This method still works with themysqli extension, as shown earlier
in the chapter.

However, mysqli also supports a new fetch method, mysqli_stmt_fetch( ). When you use
mysqli_stmt_fetch( ), PHP variables are populated with MySQL table data, in a fashion similar to
how mysqli_stmt_execute( ) pulls in values stored in PHP variables for a query, as shown in
Example 3-4.

Example 3-4. Querying with bound output parameters

// Prepare statement

$sql = 'SELECT username FROM users';

$stmt = mysqli_stmt_init($db);

if (mysqli_stmt_prepare($stmt, $sql)) {
// Bind result variables

mysqli_stmt_bind_result($stmt, $username);

// Execute prepared statement

mysqli_stmt_execute($stmt);

// Place query data into bound result variables

while (mysqli_stmt_fetch($stmt)) {

// Print results

print "$username\n";

rasmus

zeev

The query is prepared like the earlier examples, but before executing the prepared statement, you
bind the output using mysqli_stmt_bind_result( ). The first parameter is the statement handle;
the other parameters are the variables.

In this case, since the query returns only one column, $username is the only other argument. If the
query returned multiple columns, then those variables would follow. For example, this query returns
the username and the password:

// Prepare statement

$sql = 'SELECT username, password FROM users';

$stmt = mysqli_stmt_init($db);

if (mysqli_stmt_prepare($stmt, $sql)) {

// Bind result variables

mysqli_stmt_bind_result($stmt, $username, $password);

}
Since the query can return multiple rows, mysqli_stmt_fetch( ) is placed inside a while loop. Each
time the function is called, another row is placed in the output parameters. When the data is
exhausted, the function returns NULL.

Of course, there's an OO version of Example 3-4:

$sql = 'SELECT username FROM users';

$stmt = $db->stmt_init( );

if ($stmt->prepare($sql)) {

// Bind result variables

$stmt->bind_result($username);

// Execute prepared statement

$stmt->execute( );

// Place query data into bound result variables

while ($stmt->fetch( )) {

// Print results

print "$username\n";

This example is similar to the OO code for handling bound input parameters, but now you alsouse
$stmt->bind_result( ) and $stmt->fetch( ).

3.5.5 PHP 5: Bound Input and Output Parameters


You can bind both input and output parameters to the same query. For instance, the code inExample
3-5 gets all the usernames for people with a specific Zip Code.

Example 3-5. Querying with both bound input and bound output
parameters

$zipcode = 94088;
// Prepare statement

$sql = 'SELECT username FROM users WHERE zipcode = ?';

$stmt = mysqli_stmt_init($db);

if (mysqli_stmt_prepare($stmt, $sql)) {

// Associate placeholders with data type and variable name

mysqli_stmt_bind_param($stmt, 'i', $zipcode);

// Bind result variables

mysqli_stmt_bind_result($stmt, $username);

// Execute prepared statement

mysqli_stmt_execute($stmt);

// Place query data into bound result variables

while (mysqli_stmt_fetch($stmt)) {

// Print results

print "$username\n";

rasmus

This example combines code from Examples Example 3-3 and Example 3-4. After preparing the
statement, you call both mysqli_stmt_bind_param( ) and mysqli_stmt_bind_result( ). When
you execute the query, MySQLi obeys both settings.
The inner while fetches each row using mysqli_stmt_fetch( ) and prints out the data. Here,
there's only one record-rasmus-because the dataset is quite small.

You can bind the same variables for both input and output. However, it often makes sense to use
separate variables, so you can more easily distinguish input data from output.

3.5.6 Object-Oriented Interface


You've already seen a few examples showing how to use the object-oriented interface tomysqli with
bound parameters. This section fills in the remaining details.

To complement the mysqli object, there's also a MySQLi statement object, mysqli_stmt. This object
is returned by mysqli_stmt_init( ), and its methods are all the functions that begin with
mysqli_stmt, such as mysqli_stmt_prepare( ) and mysqli_stmt_bind_param( ), but without the
mysqli_stmt prefix.

Example 3-6 rewrites Example 3-5 using the OO interface.

Example 3-6. Querying with bound parameters and an OO interface

$zipcode = 94088;

// Prepare statement

$sql = 'SELECT username FROM users WHERE zipcode = ?';

$stmt = $db->stmt_init( );

if ($stmt->prepare($sql)) {

// Associate placeholders with data type and variable name

$stmt->bind_param('i', $zipcode);

// Bind result variables

$stmt->bind_result($username);

// Execute prepared statement


$stmt->execute( );

// Place query data into bound result variables

while ($stmt->fetch( )) {

// Print results

print "$username\n";

The call to mysqli_stmt_init($db) is now $db->stmt_init( ). This method still has a leading stmt
because it's a method of the mysqli object instead of a statement object.

The other calls now invoke methods on $stmt. The statement is prepared by calling $stmt-
>prepare($sql) and bound using $stmt->bind_param('i', $zipcode) and $stmt-
>bind_result($username).

Since all the information about the statement is already stored in the object, youexecute the query
and fetch the data ($stmt->execute( ) and $stmt->fetch( )) without passing any arguments.

< Day Day Up >


< Day Day Up >

3.6 Before and After: Subselects


Subselects are a popular database feature that's available in MySQL 4.1. Asubselect , or a subquery ,
is a query that occurs within the context of another query. You then use the subselect's results in the
main query.

Many developers like subselects because they allow them to chain queries together to winnow results.
It's often possible to rewrite a query to eliminate a subselect; however, this is not always
straightforward or efficient. Additionally, sometimes, without a subselect, you'll be forced to make
multiple requests.

The following sections contain a few examples that show how a subselect can solve problems. Many of
them use the following programs table:

mysql> DESCRIBE programs;

+------------+-----------------+------+-----+---------+----------------+

| Field | Type | Null | Key | Default | Extra |

+------------+-----------------+------+-----+---------+----------------+

| id | int(5) unsigned | | PRI | NULL | auto_increment |

| title | varchar(50) | | | | |

| channel_id | int(5) unsigned | | | 0 | |

+------------+-----------------+------+-----+---------+----------------+

3 rows in set (0.00 sec)

mysql> SELECT * FROM programs;

+----+-----------------+------------+

| id | title | channel_id |

+----+-----------------+------------+

| 1 | Oprah | 60 |

| 2 | Sex and the City| 201 |


| 3 | The Sopranos | 201 |

| 4 | Frontline | 13 |

+----+-----------------+------------+

4 rows in set (0.00 sec)

3.6.1 MySQL 4.0: Finding Related Entries


A common database-related task is finding rows that match a set of criteria. Normally, these
specifications are known ahead of time:

// Find the names and address of all people

// who have an email address ending in "php.net".

SELECT name, email FROM users WHERE email LIKE '%.php.net';

// Find the title of all TV programs that air on channel 13

SELECT title

FROM programs, channels

WHERE channel.name = 'HBO'

AND program.channel_id = channel.id;

Sometimes, as in the first case, you only need to query a single table. Other times, as in the second
example, you need to link two tables together based on a common key to gather the information you
want.

However, these links aren't always sufficient. For example, you know there's a show calledThe
Sopranos and you want to discover all the other shows that air on the same channel, but you don't
know which channel that is. One solution is to use two queries:

// Get channel_id:

$r = $db->query("SELECT channel_id FROM programs

WHERE title = 'The Sopranos'");

$row = $r->fetch_assoc( );
// Use channel_id to find programs:

$s = $db->query("SELECT title FROM programs

WHERE channel_id = $row[channel_id]");

while ($program = $s->fetch_assoc( )) {

print $program['title'] . "\n";

Sex and the City

The Sopranos

This is easy to understand, but it's a bit wasteful to make two queries for such a simple request.
Another solution is to employ a self-join:

// All-in-one query using self-join:

$s = $db->query("SELECT p2.title

FROM programs AS p1,

programs AS p2

WHERE p1.title = 'The Sopranos'

AND p1.channel_id = p2.channel_id");

while ($program = $s->fetch_assoc( )) {

print $program['title'] . "\n";

Sex and the City

The Sopranos

It can seem a little odd at first to JOIN a table against itself, but it can actually come in quite handy.
In this example, you're finding the set of shows that have a title ofThe Sopranos and cross-
referencing them against all the shows have the same channel_id field.

In order to eliminate the ambiguity of which table you're referring to, you're forced to assigntable
aliases. The table you're using to locate the channel The Sopranos is on is p1 , and the table in which
you're searching for matching shows is p2 . That's why you return p2.title .

To return all the rows in p2 , use p2.* . This syntax is similar to * , which returns all rows, but
qualifies it with a table name.

Since you're joining on the same column, an equivalent query is:

// All-in-one query using self-join and USING( )

$s = $db->query("SELECT p2.title

FROM programs AS p1 INNER JOIN programs AS p2

USING (channel_id)

WHERE p1.title = 'The Sopranos'");

while ($program = $s->fetch_assoc( )) {

print $program['title'] . "\n";

Sex and the City

The Sopranos

An INNER JOIN is identical to linking tables with a comma (,), and the USING( ) condition is
shorthand for a list of columns to set as equal. Thus, USING(channel_id) is the same as
p1.channel_id = p2.channel_id .

3.6.2 MySQL 4.1: Finding Related Entries


Still, that type of query can get hard to handle as the relationships become more complex. Many
people find it easier to implement that request using a subselect:

// All-in-one query using subselect:

$s = $db->query("SELECT title FROM programs WHERE channel_id =

(SELECT channel_id FROM programs

WHERE title = 'The Sopranos' LIMIT 1)");

while ($program = $s->fetch_assoc( )) {

print $program['title'] . "\n";

Sex and the City


The Sopranos

MySQL starts off by parsing the SQL statement. When it reaches the second SELECT , it places what it
already has on hold and executes the second query:

mysql> SELECT channel_id FROM programs WHERE title = 'The Sopranos' LIMIT 1;

+------------+

| channel_id |

+------------+

| 201 |

+------------+

1 row in set (0.00 sec)

That query returns a number (or NULL , if there are no matches). For the sake of example, assume
it's 201 . This then translates the original query into:

mysql> SELECT title FROM programs WHERE channel_id = 201;

+-----------------+

| title |

+-----------------+

| Sex and the City|

| The Sopranos |

+-----------------+

2 rows in set (0.00 sec)

MySQL can now execute the query and return your titles.

Notice how the subselect specifically adds a LIMIT clause to restrict the number of rows to 1 . Since
the results are used in the context of channel_id = , returning more than one number leads to a
parser error because it's like doing channel_id = 201, 202 .

When you expect your query will return multiple rows, switch from an equals sign (= ) to IN( ) :

mysql> SELECT title FROM programs WHERE channel_id IN(

SELECT channel_id FROM programs WHERE title = 'The Sopranos');


+-----------------+

| title |

+-----------------+

| Sex and the City|

| The Sopranos |

+-----------------+

2 rows in set (0.02 sec)

This allows you to find programs located in any of the channels returned by the subselect. Be careful,
though-duplicate items are eliminated when you use IN( ) . Sometimes this is good, but not always.

For instance, say you're implementing the backend of a TiVo or another DVR. You want to track all
the shows that have been watched so you can suggest related programs the person might also be
interested in viewing. This table, viewings , contains a list of program_id s. Each number is a key in
the programs table from before and uniquely represents a program.

Here's one potential dataset:

mysql> SELECT * FROM viewings;

+------------+

| program_id |

+------------+

| 1 |

| 2 |

| 1 |

| 3 |

| 4 |

+------------+

5 rows in set (0.01 sec)

There are five shows, but program 1 was watched twice. Here's what happens if you look up the titles
using IN( ) :

mysql> SELECT title FROM programs WHERE id IN


(SELECT program_id FROM viewings);

+-----------------+

| title |

+-----------------+

| Oprah |

| Sex and the City|

| The Sopranos |

| Frontline |

+-----------------+

4 rows in set (0.01 sec)

There are only four records because the duplicate program ID of 1 isn't counted twice. To find out
which show was so good as to merit a second viewing, you need to eliminate the subselect and return
to a join:

mysql> SELECT title FROM programs INNER JOIN viewings

ON viewings.program_id = programs.id;

+-----------------+

| title |

+-----------------+

| Oprah |

| Frontline |

| Oprah |

| Sex and the City|

| The Sopranos |

+-----------------+

5 rows in set (0.01 sec)

Ah! Now you can see that someone really enjoys Oprah , so you're more likely to suggest related
shows.
3.6.3 When Subselects Are Necessary
The previous examples showed different ways to rewrite your query. While using a subselect often
made the query easier to write and understand, it was never necessary. There was always an
alternative syntax that used a JOIN to eliminate the subselect, yet retained the same results.

That's not always the case. There are lots of examples where you just can't solve the problem in a
single query without using a subselect. This normally occurs when you're using anaggregate function,
such as COUNT( ) .

Returning to the last problem, you've already seen how repeated entries in a table can cause
problems when using IN( ) , but they can give you trouble in other ways, too. For instance, you want
to find the title s of the shows you've watched more than The Sopranos . Once again, the
viewings table looks like this:

mysql> SELECT * FROM viewings;

+------------+

| program_id |

+------------+

| 1 |

| 2 |

| 1 |

| 3 |

| 4 |

+------------+

5 rows in set (0.01 sec)

To find the total number of times you've watched a show, you need to do a join against theprograms
table and count the number of matches:

mysql> SELECT COUNT(*) FROM viewings LEFT JOIN programs ON(program_id = programs.id)

WHERE programs.title = 'The Sopranos';

+---+

| c |

+---+
| 1 |

+---+

1 row in set (0.00 sec)

Place this result as a subselect inside the master query:

mysql> SELECT title, COUNT(*) AS c

FROM viewings LEFT JOIN programs

ON (program_id = programs.id)

GROUP BY title

HAVING c > ( SELECT COUNT(*)

FROM viewings

LEFT JOIN programs

ON (program_id = programs.id)

WHERE title = 'The Sopranos');

+-------+---+

| title | c |

+-------+---+

| Oprah | 2 |

+-------+---+

1 row in set (0.00 sec)

Once again, Oprah is the most popular show.

This question can't be rewritten to avoid a subselect, because the condition inside the subselect not
only uses an aggregate function, such as COUNT( ) in this example, but also places restrictions upon
the query using a WHERE clause. Since you only want the WHERE title = 'The Sopranos ' condition
to apply to the subselect's COUNT(*) , it can't mix with the SELECT title, COUNT(*) in the rest of
the query.

For more on aggregate functions and HAVING clauses, see the discussion in Section 4.6 .

< Day Day Up >


< Day Day Up >

3.7 Transactions
MySQL 4.0 supports database transactions. Transactions allow you to group together a sequence of
database queries to ensure that MySQL either completes the entire set of actions or fails to perform
any of them. There's no in-between state.

A perfect example demonstrating the importance of transactions is transferring money from a bank
account. Removing money takes two steps: first, the program checks whether the account contains
enough funds; if it does, it then subtracts the money from the account and places it in another.

You don't want a second withdrawal to occur in between checking the account balance and removing
the funds, because the other transaction could completely empty the account, leaving no more
money for your request. This is a big problem for banks.

3.7.1 Creating Transaction-Supported Tables


In order to support transactions, MySQL introduced a new database table format,called InnoDB. The
original format, MyISAM, can't be used with transactions. If you're running MySQL 4.0 or higher, you
should have support for InnoDB tables. To check, run the following MySQL query:

mysql> SHOW VARIABLES LIKE 'have_innodb';

+---------------+-------+

| Variable_name | Value |

+---------------+-------+

| have_innodb | YES |

+---------------+-------+

It should say YES. By default, MySQL enables InnoDB tables as of Version 4.0, so if this variable is set
to NO, you should rebuild MySQL and remove the --without-innodb flag from your configuration.

MySQL also supports transactions with Berkeley DB tables in MySQL 3.23, but
that's not covered here.

To create an InnoDB table, add TYPE = InnoDB to the end of the CREATE statement. For example, to
replicate the programs table:
CREATE TABLE programs (id int(5) unsigned AUTO_INCREMENT,

title VARCHAR(50) not null,

channel_id int(5) unsigned not null,

primary key(id))

TYPE = InnoDB;

This makes a programs table that supports transactions.

You can make MySQL convert an existing MyISAM table to the new format with the following
command:

ALTER TABLE programs TYPE = InnoDB;

This converts programs to InnoDB. Substitute your table names for programs.

3.7.2 Using Transactions


To signal to MySQL that you want to begin a transaction, use the keywordBEGIN. (BEGIN WORK and
START TRANSACTIONS are synonyms for BEGIN.) To end a transaction, use COMMIT. In PHP, you pass
these keywords as part of your SQL inside of mysqli_query( ):

$user = array('rasmus', 'z.8cMpdFbNAPw');

mysqli_query($db, 'BEGIN');

foreach ($users as $user) {

mysqli_query($db, "INSERT INTO users VALUES(

'$user[0]', '$user[1]')";

mysqli_query('COMMIT');

The SQL opens with BEGIN, and then PHP iterates through an array, creating a series of INSERTs.
When the loop is done, COMMIT is sent.

This transaction will enter a group of user records into a database. It's unlikely to have any errors
that require you to roll back the data. However, assume the users table requires that each username
entry be UNIQUE. Then, this array causes a problem:
$users = array(array('rasmus', 'z.8cMpdFbNAPw'),

array('zeev' , 'asd34.23NNDeq'),

array('rasmus', 'z.8cMpdFbNAPw'));

Since there are two entries in the array with a username of rasmus, MySQL issues an error when you
attempt to enter the second rasmus into the table.

Here's the SQL to create that table:

CREATE TABLE users(username VARCHAR(50) UNIQUE,

password VARCHAR(50))

TYPE = InnoDB

Now you can structure your code to catch the error and revert the transaction, as in Example 3-7.

Example 3-7. Querying using transactions

$error = 0;

// Start transaction

mysqli_query($db, 'BEGIN');

// Prepare INSERT statement

$sql = 'INSERT INTO users VALUES(?, ?)';

$stmt = mysqli_stmt_init($db);

mysqli_stmt_prepare($stmt, $sql);

mysqli_stmt_bind_param($stmt, 'ss', $username, $password);

// Add each new user one-by-one

foreach ($users as $user) {


list($username, $password) = $user;

// Abort if there's an error

if (!mysqli_stmt_execute($stmt)) {

$error = mysqli_errno($db);

break;

// Revert previous commits on error; otherwise, save

if ($error) {

mysqli_query($db, 'ROLLBACK');

} else {

mysqli_query($db, 'COMMIT');

The mysqli_query('BEGIN'); starts the transaction. The foreach( ) iterates over the $users
array. Inside, the bound variables are set to the correct elements of$users and the query is
executed. Since mysqli_stmt_execute( ) returns true on success and false on failure, there's no
need to check the return value of mysqli_errno( ) for every request. Instead, call it when you know
there's an error, set $error to the return code, and break out of the foreach.

Since mysqli_errno( ) always returns a positive number when there's an error, you can use the
value of $error to decide whether to commit or roll back the transaction. When $error is positive, it
evaluates as true and you should issue the ROLLBACK command to revert the transaction. When it's
0, pass mysqli_query( ) COMMIT to store the rows in the database.

3.7.3 AUTOCOMMIT
By default, MySQL automatically commits any command you issue, unless it's wrapped inside of a
transaction. However, you can reverse this behavior for InnoDB tables, so that commands are
presumed to be part of a transaction and are flushed only when COMMIT is sent.

Enable this feature with the following command:

// Disable AUTOCOMMIT
mysqli_autocommit($db, false);

// This SQL statement is identical to the above function

SET AUTOCOMMIT = 0;

Now your queries will be reflected in your current connection, but won't be saved unless you issue a
COMMIT command. Example 3-8 is a modified version of the last script, Example 3-7, that uses this
feature.

Example 3-8. Querying with AUTOCOMMIT disabled

$error = 0;

// Disable AUTOCOMMIT

mysqli_autocommit($db, false);

// Prepare INSERT statement

$sql = 'INSERT INTO users VALUES(?, ?)';

$stmt = mysqli_stmt_prepare($db, $sql);

mysqli_stmt_bind_param($stmt, 'ss', $username, $password);

// Add each new user one-by-one

foreach ($users as $user) {

list($username, $password) = $user;

// Abort if there's an error

if (!mysqli_stmt_execute($stmt)) {
$error = mysqli_errno($db);

break;

// Save if not an error

if (!$error) {

mysqli_query($db, 'COMMIT');

Since AUTOCOMMIT is disabled, there's no need to ROLLBACK the transaction at the end of the code.

You can check the current status of AUTOCOMMIT with:

mysql> SELECT @@AUTOCOMMIT;

+--------------+

| @@AUTOCOMMIT |

+--------------+

| 1 |

+--------------+

1 row in set (0.00 sec)

The double at signs (@@) tell MySQL to retrieve a MySQL-specific variable instead of a user-defined
variable.

< Day Day Up >


< Day Day Up >

3.8 Before and After: Making Multiple Queries


In PHP 4, when you need to make more than one query in a row, you're forced to callmysql_query(
) once for each query. This often happens when you need distinct pieces of information from MySQL
or when you want to create a fresh set of tables.

This isn't a big problem when you know your queries ahead of time, because it's easy to loop through
and send them to MySQL one at a time. However, for some situations, this is not the case. For
example, you're writing a PHP frontend to MySQL, such as phpMyAdmin
(http://www.phpmyadmin.net), and want the ability to take a database dump and recreate the
information.

Without the ability to send the entire dump at once, you're required to parse the data into individual
statements. That's not as easy as it sounds, because you can't just split on the semicolon (;). For
example, it's perfectly valid to have a line like INSERT INTO users VALUES('rasmus',
'pass;word');. Since pass;word is inside single quotes, MySQL knows it doesn't signal the end of a
statement, but a simple split isn't smart enough to understand this. As a result, you're effectively
forced to write a MySQL parser in PHP.

The restriction of one query per request is lifted in MySQLi. This actually wasn't a limitation in PHP,
but a deficit in the protocol used by earlier versions of MySQL. Another benefit of MySQL 4.1's
updated protocol is the ability to introduce a mysqli_multi_query( ) function for these types of
cases.

While this is good news, it also introduces a greater potential for SQL injection attacks. AnSQL
injection attack is when a malicious person passes in data that alters your SQL so that you end up
issuing a different (and probably more dangerous) SQL query than what you expected.

Here's an example that's supposed to return the bank account information for only a single user:

$sql = "SELECT account_number, balance FROM bank_accounts

WHERE secret_code LIKE '$_GET[secret_code]'";

You know that it's important to use a secret code so that people can't access other people's
information by incrementing a primary key ID number, because those numbers are easy to guess.
However, you've forgotten to call mysql_real_escape_string( ) on $_GET['secret_code'], and
this is trouble. A cracker can set secret_code to ' OR '1' LIKE '1, and that turns your query into:

SELECT account_number, balance FROM bank_accounts

WHERE secret_code LIKE '' OR '1' LIKE '1';

Since any string always matches itself, this effectively alters the query to returnall rows. If there's
any positive to this exploit, it's that since MySQL only allows you to execute a single query at a time,
you cannot radically alter the query.

However, now that you can issue multiple queries, you're vulnerable to something even worse.
People can issue any query they want:

SELECT account_number, balance FROM bank_accounts

WHERE secret_code LIKE '';

UPDATE bank_accounts

SET balance = 1000000

WHERE account_number = '12345678';

By altering secret_code to '; UPDATE bank_accounts SET balance = 1000000 WHERE


account_number = '12345678';, you terminate the first query and append another. The second
query isn't limited by the constraints of the first, so a query that's supposed toSELECT data now also
UPDATEs it (or data in any other table).

To prevent this security vulnerability, mysqli forcibly disables mysqli_query( )'s ability to make
multiple queries. Instead, there's an alternate set of functions for you to use.

3.8.1 mysql: Making Multiple Queries


In mysql, the best way to handle multiple queries is to place them in individual array elements. You
can loop then through the array and issue the queries.

Example 3-9 creates a users table and populates it with rasmus and zeev accounts.

Example 3-9. Executing multiple queries with mysql_query( )

$queries = array(

"DROP TABLE IF EXISTS users;",

"CREATE TABLE users(username VARCHAR(50) UNIQUE,

password VARCHAR(50));",

"INSERT INTO users VALUES('rasmus', 'z.8cMpdFbNAPw'),

('zeev', 'asd34.23NNDeq');",

"SELECT * FROM users WHERE username LIKE 'rasmus';");


foreach ($queries as $query) {

if ($result = mysql_query($query, $db) and $result != = true) {

while ($row = mysql_fetch_row($result)) {

print "$row[0]\n";

It's important to quote the SQL statements properly because they often have embedded single and
double quotation marks. In this case, the INSERT and SELECT statements both have single quotes.

The foreach iterates through each element and calls mysql_query( ). If everything goes okay, the
function returns a true value. When there are rows to process, such as in aSELECT or DESCRIBE
query, mysql_query( ) returns a resource. When the request was successful but there's no
additional information, it just returns true.

This allows you to distinguish between queries where you can callmysql_fetch_row( ) by placing
and $result != = true inside the conditional. You must use both and instead of && and != =
instead of !=; otherwise, you get different results than expected because of operator precedence and
type coercion issues.

Finally, the while loop retrieves each row and prints out the first column. Since only one of the four
statements made in this example returns results and it returns only a single row, the entire output of
this loop is rasmus.

Of course, as said earlier, none of this is necessary if your SQL isn't separated into individual
statements.

3.8.2 mysqli: Making Multiple Queries


The mysqli extension solves many of the problems incurred when making multiple queries. Most
importantly, you don't need to break the SQL apart. This not only eliminates the need for a MySQL
parser, but also allows you to more easily place SQL inside your script without worrying about
quotation marks.

The downside, however, is that the code to process a request is significantly more complex. Instead
of a single function, mysql_query( ), four different functions are necessary to navigate through the
results.

Use mysqli_multi_query( ) to send the query itself and mysqli_store_result( ) to load the
results of each successive query into a position where they can be fetched. When you're done with a
query, free it with mysqli_free_result( ) and move onto the next one using
mysqli_next_result( ).
Example 3-10 is a revised version of Example 3-9 showing how these functions work in combination.

Example 3-10. Executing multiple queries with mysqli_multi_query( )

$query = <<<_SQL_

DROP TABLE IF EXISTS users;

CREATE TABLE users(username VARCHAR(50),

password VARCHAR(50));

INSERT INTO users VALUES('rasmus', 'z.8cMpdFbNAPw'),

('zeev', 'asd34.23NNDeq');

SELECT * FROM users WHERE username LIKE 'rasmus';

_SQL_;

if (mysqli_multi_query($db, $query)) {

do {

if ($result = mysqli_store_result($db)) {

while ($row = mysqli_fetch_row($result)) {

print "$row[0]\n";

mysqli_free_result($result);

} while (mysqli_next_result($db));

Your SQL is no longer stored in an array, but as a string. This lets you use a heredoc to avoid the
problem of single and double quotes.

The mysqli_multi_query( ) function takes a database link and a query, just like mysqli_query( ).
If anything goes wrong, such as a parse error, it returns false; otherwise, it places the first result
set into position for retrieval and returns true.

Since mysqli_multi_query( ) has already handled the first result, use a do/while loop instead of a
while to iterate through all the data. Otherwise, the first call to mysqli_next_result( ) will actually
load the second result.

Inside the loop, call mysqli_store_result( ) to transfer each result set to PHP. If the query doesn't
return any rows, then mysqli_store_result( ) returns false. This actually happens for the first
three queries in this example-the DROP, CREATE, and INSERT. Only the SELECT statement causes a
non-false response.

This behavior is different from mysql_query( ) and is actually more useful because it allows you to
eliminate the and $result != = true check from the code.

Now you can use the standard set of retrieval functions, such as mysqli_fetch_row( ), to process
the data. This example just prints out the first column from every row retrieved.

Be sure to call mysqli_free_result( ) to release the memory when you're done with the result.
Otherwise, it will pile up until the script finishes running. Since it's easy to make numerous queries
using mysqli_multi_query( ), it's easy to quickly use up lots of RAM.

To check if there are additional results without advancing the counter by calling
mysqli_next_result( ), call mysqli_more_results( ):

if (mysqli_multi_query($db, $query)) {

do {

if ($result = mysqli_store_result($db)) {

while ($row = mysqli_fetch_row($result)) {

print "$row[0]\n";

mysqli_free_result($result);

// Add divider after all results except the last

if (mysqli_more_results($db)) {

print str_repeat('-', 10) . "\n";

} while (mysqli_next_result($db));

}
The mysqli_more_results( ) function returns the same value as the next call to
mysqli_next_result( ). This lets you add dividers or perform some other kind of processing after
all statement results except the final one.

If you're put off by the complexity of this code, there's an alternative that combines the benefits of
multiple queries with the simplicity of the earlier syntax. TheSection 6.3 contains a MySQL multi-
query iterator that encapsulates all the logic into a reusable class and lets youforeach through the
results.

< Day Day Up >


< Day Day Up >

3.9 Securing Connections with SSL


You can now encrypt the connections between PHP and MySQL. Normally, MySQL does all
communication in plain text because it's the fastest way to send data. However, MySQL 4.0 lets you
use SSL encryption to prevent people from spying on traffic between PHP and MySQL, and MySQL 4.1
extends this to include replication over SSL.

An SSL-enabled version of MySQL doesn't require you to use SSL for all your connections. You can
set different permission levels on your accounts, so you can place varying restrictions as you see fit.
For some accounts, you may not want the hassle of dealing with SSL and authentication.

Once all your systems are configured, it's quite easy to use SSL with MySQLi. Getting everything up
and running can be a bit of a struggle because you need to add OpenSSL support to both MySQL and
PHP, create SSL certificates for MySQL, and also properly configure your MySQL user accounts and
configuration files.

Here's a list of what you need to do:

1. Install OpenSSL if your system doesn't already have it.

2. Add SSL support to MySQL and reinstall.

3. Add SSL support to PHP, link against the new MySQL client, and reinstall.

4. Create SSL certificates for the MySQL server.

5. Add SSL certificate information to your my.cnf files.

6. Restart MySQL.

7. Edit the MySQL GRANT table to require SSL.

8. Connect to MySQL from PHP using SSL.

Before you can do anything, you need to have OpenSSL on your machine. Most systems come with
OpenSSL preinstalled, but you can download a copy from http://www.openssl.org/.

The second step is to check whether your MySQL server already supports SSL. Issue the following
command:

SHOW VARIABLES LIKE 'have_openssl';

If it returns YES, you're in luck; otherwise, you must reconfigure MySQL. Modify your previous
configuration to add --with-openssl and --with-vio. (If you downloaded a prebuilt MySQL
package, you will need to download the MySQL source and follow the installation instructions.) After
you've rebuilt MySQL, install it.
Now add OpenSSL to PHP and get mysqli to enable its SSL support. This is done by adding the --
with-openssl configuration option to PHP. Even if your copy of PHP already has SSL support, you
still must remake PHP if your MySQL server didn't support SSL when you originally built PHP. When
you're done, go ahead and reinstall PHP, restarting your web server if necessary.

Even though both PHP and MySQL now have SSL support, this doesn't mean they'll automatically use
it. Before this can happen, you need to create SSL certificates for MySQL. See
http://dev.mysql.com/doc/mysql/en/Secure_create_certs.html for a step-by-step guide if you're
unfamiliar with OpenSSL and certificates.

Once the certificates are created, add the SSL certificate information to yourmy.cnf files. This allows
MySQL to load in the configuration settings during startup. Here's an example:

[client]

ssl-ca=/usr/local/mysql/openssl/cacert.pem

ssl-cert=/usr/local/mysql/openssl/client-cert.pem

ssl-key=/usr/local/mysql/openssl/client-key.pem

[mysqld]

ssl-ca=/usr/local/mysql/openssl/cacert.pem

ssl-cert=/usr/local/mysql/openssl/server-cert.pem

ssl-key=/usr/local/mysql/openssl/server-key.pem

This adds the key, certificate, and certificate authority (CA) files to MySQL. You can also set a
pathname to trusted SSL CA certificates using the ssl-capath configuration option and provide a list
of ciphers using ssl-ciphers.

The next step is to restart the MySQL server. The have_openssl variable should now be YES.

Now the entire foundation is complete and you're ready to force SSL connections on your users.
MySQL lets you handle this on a per-user basis with itsGRANT tables, as you may want to allow some
local users to make unencrypted connections. To require a basic SSL connection, addREQUIRE SSL to
the end of your GRANT statement. For example:

GRANT ALL PRIVILEGES ON users.* TO 'ssl-user'@'localhost'

IDENTIFIED BY 'password' REQUIRE SSL;

The web page at http://dev.mysql.com/doc/mysql/en/Secure_GRANT.html covers all the different


ways you can make MySQL enforce an SSL connection and gives a few additional sampleGRANT
commands.

Everything is now complete and you're ready to make an SSL connection from PHP usingmysqli.
This can be done in two different ways, but both methods require you to usemysqli_real_connect(
).

The easiest way is to read the SSL settings from a my.cnf file:

$db = mysqli_init( );

mysqli_options($db, MYSQLI_READ_DEFAULT_FILE, '/etc/my.cnf');

mysqli_real_connect($db, 'localhost', 'ssl-user', 'password', 'database');

The MYSQLI_READ_DEFAULT_FILE option forces MySQL to read in the contents of the client group in
/etc/my.cnf. This allows you to set all your SSL options in a single place and use them from PHP, the
mysql command-line tool, or other programs.

You can also specify the options using mysqli_ssl_set( ):

$db = mysqli_init( );

mysqli_ssl_set($db, '/usr/local/mysql/openssl/server-key.pem',

'/usr/local/mysql/openssl/server-cert.pem',

'/usr/local/mysql/openssl/cacert.pem',

NULL,

NULL);

mysqli_real_connect($db, 'localhost', 'ssl-user', 'password', 'database');

The mysqli_ssl_set( ) function takes six arguments. The first is the database link and the next five
are the SSL key, certificate, CA certificate, CA path, and cipher. If your encryption method doesn't
require one or more of these parameters, pass NULL in their place.

This function always returns true. SSL errors are reported when you make the actual connection to
MySQL.

Using mysqli_ssl_set( ) overrides any variables set with the MYSQLI_READ_DEFAULT_FILE option,
so you can still read in a configuration file for general settings, but alter your SSL information if
necessary.

Both techniques automatically set the MYSQLI_SSL_CLIENT flag, so you don't need to add this to your
call to mysqli_real_connect( ).

< Day Day Up >


< Day Day Up >

3.10 Porting Code and Migrating Databases


Although the new mysqli extension has many benefits over the older extension, they come at a cost.
Depending upon your code, porting to the new extension can be a surprisingly tiresome task. If
you've used a database abstraction layer-such as PEAR DB, ADOdb, or MDB-this task is simplified
considerably. Even though you're only switching from one version of MySQL to another, the new
extension is sufficiently different that quite a bit of work is necessary to make the switch.

However, you probably will want to use mysqli for your new projects, and that requires an upgrade
to MySQL 4.1. Since an out-of-the-box MySQL 4.1 is incompatible withmysql, this presents a bit of a
dilemma. You want the benefits of mysqli without worrying about legacy code.

Another option is just to make the switch to a database abstraction layer. This has some advantages
and some disadvantages. The primary benefit is portability, but this comes at a cost of speed and
features. For instance, you cannot bind output parameters with PEAR DB, nor does DB support all of
the latest MySQLi options, such as mysqli_multi_query( ) and ssl_set( ).

This section presents a variety of options for handling the conversion process. Each one has various
positive and negative attributes. Some require more hours of work; others are faster to implement,
but at a cost of execution speed. None of these solutions are bad in and of themselves, but, given
certain circumstances, some should be preferable to others. In the end, you must weigh the trade-
offs and make the decision for yourself.

3.10.1 Migration Paths


The biggest problem is that you're really making three migrations instead of one. You're migrating
from:

PHP 4 to PHP 5

MySQL 3.2.x (or MySQL 4.0) to MySQL 4.1

mysql to mysqli

All three migrations are non-trivial and require careful planning. Trying to do all three simultaneously
is very risky. However, with preparation, there are a number of ways to handle the problem so the
task does not become unmanageable.

3.10.1.1 Don't migrate anything

The fastest migration path is making a clean break between your old code and your new code.
Legacy code runs under PHP 4, MySQL 3.2.x or MySQL 4.0.x, and mysql. New code runs under PHP
5, MySQL 4.1, and mysqli.
The advantage is no wasted time; however, you must replicate your entire setup-web server,
database server, document trees-and run two parallel systems. (Alternatively, you can run one
version of PHP as a CGI. See Appendix C for more details.) This presents additional administrative
overhead because you have twice as many pieces of software to maintain.

Additionally, if you have even one piece of code that needs to run in both environments, you may end
up making a majority of the changes anyway. Therefore, this solution is suggested only for people
with discrete scripts that are written for a single project and not supported on an ongoing basis,
except for minimal maintenance.

3.10.1.2 Migrate only to MySQL 4.1

The second easiest migration path is to keep separate systems like before but switch to a common
MySQL 4.1 backend. This allows you to keep your databases in sync because both older PHP 4
projects and newer PHP 5-based projects can share the same database tables.

While your old projects cannot use the new mysqli extension, because they're still using PHP 4, they
can still take advantage of server-level features in MySQL 4.1, such as subselects and transactions,
that are not dependent on the client.

This solution does require a bit of work. In addition to installing MySQL 4.1, you must move your
database tables to the new server. This process is not too difficult, but it's not backward compatible.
MySQL 3.2.x uses the ISAM table format to store data; MySQL 4.0 and above use the MyISAM
format. Although MySQL 4.0 supports both types of tables, MySQL 4.1 does not support the old
format unless you specifically request it. Additionally, theInnoDB format used by MySQL for
transactions is unavailable under most pre-4.1 versions of MySQL.

There is another downside. Specifically, MySQL 4.1 has a new password schema, so you must run
MySQL with the --old-passwords flag if you want to use mysql with MySQL 4.1. The upgraded
password system is more secure than earlier versions; however, it's incompatible with older MySQL
clients, such as the one bundled with PHP 4. Therefore, MySQL provides an option to downgrade the
authentication mechanism, to be backward compatible at the expense of some security.

This does not mean running MySQL 4.1 with --old-passwords is insecure. It's actually more secure
than MySQL 4.0, but it's not as secure as a regular MySQL 4.1 configuration.

3.10.1.3 Migrate to MySQL 4.1 and PHP 5

A major disadvantage to the two earlier solutions is that you cannot run PHP 4 and PHP 5 as modules
on the same web server. (You can set one up as a module and the other as a CGI, but that is not
acceptable for most production environments.)

This means you need to run two versions of Apache (or whatever web server you're using). These
servers must either have different hostnames or use different ports on the same hostname. Also,
your old code cannot take advantage of any of the new features offered in PHP 5.

Luckily, a minimal port to PHP 5 is not difficult (assuming you don't make extensive use of DOM XML
or XSLT functions; see Chapter 5 for more details). Thus, you may want to move all your code to PHP
5 but continue using the mysql extension for old projects and use mysqli only for new ones.
This allows you to avoid both the hassle of switching your entire code base over tomysqli and the
pain of administering two sets of web servers and document hierarchies.

The disadvantage to this solution is that you're required to invest a good amount of time into
migrating both code and data. Plus, you need to compile both extensions into PHP, which adds to the
amount of memory taken up by your web server processes. Last, it can become confusing or
annoying to track which projects use what version of the MySQL extensions.

3.10.1.4 Complete migration to MySQL 4.1, PHP 5, and mysqli

The final choice is a 100% migration of all three products. This is by far the most complex maneuver,
especially if you're not using a database abstraction layer. Many mysql functions are altered in or
removed from mysqli; although, to be fair, most of these functions are rather esoteric.

However, you gain significant benefits from this solution: faster execution times, stronger security,
and cleaner code for all your programs. After expending this one-time cost, you never need to worry
about the old code again, because all your scripts run under the same environment.

3.10.1.5 Making the right decision

After hashing through all those choices, you can see that the weaselly answer to "What should I do?"
is "It depends." Unfortunately, sometimes the weaselly answer is also the truth.

Nevertheless, it's best to remember this is not an all-or-nothing decision that gets made once and is
then forever set in stone. There's nothing wrong with, for example, trying to see if you can get by
without porting all your old code to PHP 5. If in two months you decide that was a mistake, you can
then go ahead and make the switch.

You can always stop your migration at any time, see if you're happy with your setup, and restart it
later if need be. This approach is actually safer because it gives you time to discover bugs on a step-
by-step basis. Additionally, it may actually be faster to migrate this way because it's easier to find
bugs when you can isolate them to the latest level of changes.

That's why you're strongly encouraged to follow the path outlined next for the transition. This way,
you can come back to the book and not worry about forgetting any steps.

3.10.2 Migration Plan of Attack


Due to the various incompatibilities between PHP 4 and PHP 5,mysql and mysqli, and MySQL 4.0
and MySQL 4.1, the easiest solution is to migrate your application and data in the following order:

1. Transfer your database tables to MySQL 4.1.2 (or higher), but continue to usemysql. This
requires you to modify how MySQL 4.1 stores your password.

2. Port your code to PHP 5, but continue using mysql.

3. Switch to using the mysqli extension, but don't use any of the new features.

4.
2.

3.

4. Take advantage of the new MySQL 4.1 features available in mysqli.

Since switching databases and porting code simultaneously is a recipe for disaster, don't try to do
everything in one giant step. Instead, break the task down into bite-sized pieces, so you can ensure
each individual step is successful. Otherwise, when you encounter a bug, it will be very difficult to
track down.

This list is specifically ordered to avoid incompatibilities and ease your migration pain.

3.10.2.1 Step 1: Migrating from MySQL 3.2.x or MySQL 4.0.x to MySQL 4.1.2

The basic process for migrating from MySQL 3.2.x (or MySQL 4.0) to MySQL 4.1.2 is simple;
however, there are many minor changes between the two versions that are not documented here,
because they're relevant only to a small group of users.

"Section 2.5: Upgrading/Downgrading MySQL" in the MySQL Manual is the definitive source for all
these differences. In addition to following the directions here, you should read this section to ensure
that nothing slips through the cracks. In particular, developers using Windows or replication should
be sure to consult the manual for changes specific to their setup. The material is availableonline at
http://www.mysql.com/doc/en/Upgrade.html.

The material in this section assumes you not only have access to a command line, but are
comfortable using it. An alternative solution is to use a web-based interface, such asphpMyAdmin
(http:://www.phpmyadmin.net). However, as of now, this product has experimental support for PHP
5 and MySQL 4.1. Therefore, you must figure out how to implement these steps in those products on
your own.

Before you do anything, back up your databases. This allows you to restore your data in case
anything happens during the upgrade process. See http://www.mysql.com/doc/en/Backup.html for
how to store data.

Since MySQL 4.1 uses the MyISAM table type, you must upgrade your tables to the new format. If
you're using MySQL 4.0.x, this is easy. You can use either of these two methods to upgrade the
tables:

1. Run the mysql_convert_table_format script that comes with MySQL. This requires Perl and
DBI, and you must set a few variables at the top of the script.

2. Issue the following SQL command for each table: ALTER TABLE table_name TYPE=MyISAM;.
Substitute your table names for table_name.

If you're still using MySQL 3.2.x, it's more problematic because this version of MySQL doesn't
understand the MyISAM format. The recommended MySQL solution is to first upgrade to MySQL 4.0.
Once you've done this, you can use the methods just described. Alternatively, you can configure
MySQL 4.1 to support the older ISAM format by passing in the --with-isam flag during compilation.

This option lets MySQL 4.1 read in the databases using the old format and lets you convert tables to
MyISAM (or InnoDB). This is not a default configuration setting for MySQL 4.1, so you cannot use a
prebuilt package or installer. Therefore, although it's more work to upgrade to MySQL 4.0 before 4.1,
it allows you to avoid compiling MySQL yourself, which can be a major plus.
Before you can move onto MySQL 4.1, you also need to alter the MySQL GRANT tables. This lets
MySQL store the securer passwords. Do this even if you're not planning on usingmysqli, because it
enables a few other features. There's another script available that makes all the necessary changes,
so you need to:

1. Run the mysql_fix_privilege_tables script.

2. Since this script connects to MySQL as root, you need to provide the MySQL root password. On
MySQL 4.0 systems, do mysql_fix_privilege_tables password. If you're upgrading directly
to MySQL 4.1, do mysql_fix_privilege_tables --password=password.

3. Shut down and restart the MySQL server.

See http://www.mysql.com/doc/en/Upgrading-grant-tables.html for more details.

Now you are finally ready to switch over to MySQL 4.1.2 or higher. (MySQL 4.1.0 and 4.1.1 won't
work correctly with PHP 5 in some cases.) Go to the MySQL web site, download the server, and follow
the directions for installing it. Remember, if you're upgrading directly from MySQL 3.2.x, you must
compile your own version of MySQL to enable ISAM support.

Shut down the old version of MySQL and restart the new one. If you want to support PHP 4 or PHP 5
with mysql, add the --old-passwords flag. The MySQL Manual has more information on this topic at
http://www.mysql.com/doc/en/Password_hashing.html.

3.10.2.2 Step 2: Porting to PHP 5

When porting PHP 4 code to PHP 5, make migrating to MySQLi yourlast step, not your first. You can
still use the older MySQL extension with PHP 5, so it's not necessary to switch to MySQLi to run PHP
5. As you'll see, the only XML extension in PHP 5 that's backward compatible with PHP 4 isSAX.
DOM, XSLT, and XPath all require you to alter your code. All other major PHP 4 extensions continue
to work as-is under PHP 5. Therefore, unlike with your XML code, you have the luxury of controlling
when you should port your MySQL code.

It makes sense to convert to mysqli as the final step of your migration process. You can use your
web site under PHP 5 without mysqli; that's not true for other extensions. This strategy minimizes
the initial downtime and gets a working version of your site up for testing while you migrate to
MySQLi.

Since the rest of this book covers migrating to PHP 5, the specific details of the migration process are
omitted here to prevent infinite recursion.

3.10.2.3 Step 3: Migrating to mysqli

A two-pronged migration strategy is best because there are both API changes and new features in
mysqli. The first step is to make sure your existing code is compatible with the newmysqli API. This
finally allows you to deploy your current site under PHP 5 and MySQL 4.1, but without the oldmysql
extension.

Once that's done, you should then work new features into your code base. It's tempting to upgrade
everything at once, but this always ends up taking more time than expected.

The largest change in the API is that there is no longer a concept of the "current database." Many
mysql functions allow you to omit a database handle, automatically defaulting to the last connection.
This feature has been eliminated. Although it was convenient, it made it very difficult to update the
functions to support new MySQL features without breaking backward compatibility or requiring you to
pass the database handle anyway.

Therefore, you're now required to always pass a database connection. Additionally, this handle is now
the first function parameter. It was previously the final parameter, so it could be optional. This
location mimics the MySQL API and makes the argument order more consistent.

Besides altering these functions, another set of functions have been eliminated entirely. In most
cases, these are the more obscure mysql functions, such as mysql_field_table( ). Many can be
easily replicated with a simple SQL query; others require a few queries.

The two "most used" missing functions are mysql_pconnect( ) and mysql_escape_string( ).
Switch these to mysqli_connect( ) and mysqli_real_escape_string( ), respectively.

In addition to mysql_connect( ), whose new API has already been detailed, the functions inTable 3-
3 have all been modified to require a database handle as their first argument.

Table 3-3. MySQL functions with a changed argument list

mysql mysqli
mysql_connect([string mysqli_connect([string hostname
hostname[:port][:/path/to/socket] [, [,string username [,string password
string username [, string password [, bool [,string database_name [,int port
new [, int flags]]]]]) [,string socket]]]]]])
mysql_select_db(string database_name [, mysqli_select_db(int link_identifier,
int link_identifier]) string database_name)
mysql_query(string query [, int mysqli_query(int link_identifier,
link_identifier]) string query [,int resultmode])

mysql_fetch_field(resource result [, int


mysqli_fetch_field(object result)
field_offset])

mysql_change_user(string user, string mysqli_change_user(int link_identifier,


password [, string database [, resource string user, string password, string
link_identifier]]) database)
mysql_real_escape_string(string mysqli_real_escape_string(int
to_be_escaped [, int link_identifier]) link_identifier, string to_be_escaped)
mysqli_affected_rows(int
mysql_affected_rows([int link_identifier])
link_identifier)
mysql_close([int link_identifier]) mysqli_close(int link_identifier)

mysql_error([int link_identifier]) mysqli_error(int link_identifier)


mysql mysqli
mysql_errno([int link_identifier]) mysqli_errno(int link_identifier)

mysqli_get_host_info (int
mysql_get_host_info([int link_identifier])
link_identifier)

mysql_get_proto_info([int mysqli_get_proto_info(int
link_identifier]) link_identifier)
mysql_get_server_info([int mysqli_get_server_info(int
link_identifier]) link_identifier)

mysql_info([int link_identifier]) mysqli_info(int link_identifier)

mysql_insert_id([int link_identifier]) mysqli_insert_id(int link_identifier)


mysql_ping([int link_identifier]) mysqli_ping(int link_identifier)

mysql_stat([int link_identifier]) mysqli_stat(int link_identifier)

mysql_thread_id([int link_identifier]) mysqli_thread_id(int link_identifier)

Table 3-4 contains a list of functions not transferred to mysqli and their replacement versions. In
many cases these functions were already deprecated, so your code probably won't contain many
instances of these functions.

Table 3-4. Functions not ported to mysqli

mysql mysqli
mysql_create_db($database, $db); mysqli_query($db, "CREATE DATABASE $database");
mysql_drop_db($database, $db); mysqli_query($db, "DROP DATABASE $database");
mysql_escape_string($string); mysqli_real_escape_string($string);

$fields = mysqli_fetch_field($result);
mysql_field_flags($result, $i);
$fields[$i]->flags;
$fields = mysqli_fetch_field($result);
mysql_field_len($result, $i)
$fields[$i]->max_length;
$fields = mysqli_fetch_field($result);
mysql_field_name($result, $i)
$fields[$i]->name;
$fields = mysqli_fetch_field($result);
mysql_field_table($result, $i)
$fields[$i]->table;
$fields = mysqli_fetch_field($result);
mysql_field_type($result, $i)
$fields[$i]->type;
mysql mysqli
mysqli_data_seek($result, $i); $row =
mysql_db_name($result, $i)
mysqli_fetch_row($result);
mysql_db_query($database, $query, mysqli_select_db($db, $database);
$db); mysqli_query($db, $query);

mysql_list_dbs($db); mysqli_query($db, "SHOW DATABASES");


mysql_list_fields($database, mysqli_use_db($db, $database);
$table, $db) mysqli_query($db, "SHOW COLUMNS FROM $table");

mysql_list_processes($db) mysqli_query($db, "SHOW PROCESSLIST");

mysqli_query($db, "SHOW TABLES FROM


mysql_list_tables($database, $db)
$database");

mysql_pconnect($hostname, mysqli_connect($hostname, $username,


$username, $password); $password);

$row = mysqli_fetch_row($result);
mysql_result($result, $column);
$row[$column];

mysqli_data_seek($r, $i); $row =


mysql_tablename($result, $i)
mysqli_fetch_row($r);

mysql_unbuffered_query( ) mysqli_real_query( ); and mysqli_use_result( );

3.10.2.4 Adding new features

Once all that is finally complete, take a deep breath. Now you can move on to the fun part. Go back
over your applications and locate places where the new features can improve your code.

In particular, bound parameters can significantly speed up queries and also reduce the potential for
SQL injection attacks. You'll probably also discover places where using a subselect can reduce the
complexity of your PHP script because you've moved the logic into MySQL.

< Day Day Up >


< Day Day Up >

Chapter 4. SQLite
Substituting text files for a database is like cutting a fish with a hammer. You might get it to work,
but it's going to be a really messy process. When your application needs a server-side storage
mechanism but you can't rely upon the presence of a specific database, turn toSQLite. It correctly
handles locking and concurrent accesses, the two big headaches with home-brewed flat files.

Since the SQLite database is bundled with PHP 5, now every PHP 5 script can read, write, and search
data using SQL. SQLite differs from most databases because it is not a separate application. Instead,
SQLite is an extension that reads from and writes to regular files on the hard drive. Any PHP users
who have permission to manipulate files can use SQLite to edit a database, just like they can use GD
to edit images.

Although the name SQLite hints at a less than full-featured product, SQLite actually supports almost
all of SQL92, the SQL standard specification. Besides the usualINSERTs and SELECTs, with SQLite
you can also use transactions, query using subselects, define your own functions, and invoke
triggers.

SQLite actually performs most actions more quickly than many other popular databases. In
particular, SQLite excels at SELECTing data. If your application does an initial (or periodic) data
INSERT and then reads many times from the database, SQLite is an excellent choice. The PHP web
site uses SQLite to handle some forms of searches.

Unfortunately, SQLite has some downsides. Specifically, when you update the database by adding
new data, SQLite must lock the entire file until the alteration completes. Therefore, it does not make
sense in an environment where your data is constantly changing. SQLite does not have any
replication support, because there's no master program to handle the communication between the
master database and its slaves.

Additionally, SQLite has no concept of access control, so theGRANT and REVOKE keywords are not
implemented. This means you cannot create a protected table that only certain users are allowed to
access. Instead, you must implement access control by using the read and write permissions of your
filesystem.

SQLite is not for sites that are flooded with heavy traffic or that require access permissions on their
data. But for low-volume personal web sites and small business intranet applications, SQLite lets you
do away with the burden of database administration. SQLite is also perfect for log file analysis scripts
and other applications that benefit from a database but whose authors don't want to require the user
to install one. SQLite is bundled with PHP 5, so unless it has been specifically omitted, it's part of
every PHP 5 installation.

The SQLite home page (http://www.sqlite.org/) has more details about SQLite's features, limitations,
and internals. A list of PHP's SQLite functions is online athttp://www.php.net/sqlite.

This chapter starts off with SQLite basics: creating databases, passing SQL queries to SQLite, and
retrieving results-everything you need to start using SQLite. It then moves on to alternative SQLite
retrieval functions and interfaces, including a nifty object-oriented interface. After covering how to
talk with SQLite, this chapter shows how to improve SQLite performance with indexes and how to
gracefully handle errors. It closes with a few advanced features: transactions and user-defined
functions, which help keep your data consistent and extend SQLite, respectively.

< Day Day Up >


< Day Day Up >

4.1 SQLite Basics


It's easy to get up and running with SQLite. Its design eliminates the need for any configuration
variables, such as a database server name or a database username and password. All you need is
the name of a file where the data is stored:

$db = sqlite_open('/www/support/users.db');

sqlite_query($db, 'CREATE TABLE users(username VARCHAR(100),

password VARCHAR(100))');

This creates a users table stored in the database file located at /www/support/users.db. When you
try to open a database file that doesn't already exist, SQLite automatically creates it for you; you
don't need to execute a special command to initialize a new database.

If you cannot seem to get SQLite to work, make sure you have both read and write permission for
the location on the filesystem where you're trying to create the database.

SQLite has even fewer data types than PHP-everything's a string. While you can define a column as
INTEGER, SQLite won't complain if you then INSERT the string PHP into that column. This feature (the
SQLite manual declares this a feature, not a bug) is unusual in a database, but PHP programmers
frequently use this to their advantage in their scripts, so it's not a completely crazy idea. A column's
type matters only when SQLite sorts its records (what comes first: 2 or 10?) and when you enforce
UNIQUEness (0 and 0.0 are different strings, but the same integer).

The table created in this example has two columns: username and password. The columns' fields are
all declared as VARCHARs because they're supposed to hold text. Although it doesn't really matter
what type you declare your fields, it can be easier to remember what they're supposed to hold if you
give them explicit types.

4.1.1 Inserting Data


Add new rows to the database using INSERT and sqlite_db_query( ):

$username = sqlite_escape_string($username);

$password = sqlite_escape_string($password);

sqlite_query($db, "INSERT INTO users VALUES ('$username', '$password')");


You must call sqlite_escape_string( ) to avoid the usual set of problems with single quotes and
other special characters. Otherwise, a password of abc'123 will cause a parser error. Don't use
addslashes( ) instead of sqlite_escape_string( ), because the two functions are not equivalent.

4.1.2 Retrieving Data


To retrieve data from an SQLite database, call sqlite_query( ) with your SELECT statement and
iterate through the results:

$r = sqlite_query($db, 'SELECT username FROM users');

while ($row = sqlite_fetch_array($r)) {

// do something with $row

By default, sqlite_fetch_array( ) returns an array with the fields indexed as both a numeric array
and an associative array. For example, if this query returned one row with ausername of rasmus, the
preceding code would print:

Array (

[0] => rasmus

[username] => rasmus

As you can see, sqlite_fetch_array( ) works like mysqli_fetch_array( ).

When you're using user-entered data in a WHERE clause, in addition to calling


sqlite_escape_string( ), you must filter out SQL wildcard characters. The easiest way to do this
is with strtr( ):

$username = sqlite_escape_string($_GET['username']);

$username = strtr($username, array('_' => '\_', '%' => '\%'));

$r = sqlite_query($db,

"SELECT * FROM users WHERE username LIKE '$username'");

Use sqlite_num_rows( ) to find the total number of rows returned by your query without iterating
through the results and counting them yourself:
$count = sqlite_num_rows($r);

You can call sqlite_num_rows( ) without retrieving the results from SQLite. Remember, this
function takes the query result handle, like sqlite_fetch_array( ).

If speed is a concern, use sqlite_array_query( ). This retrieves all the data and puts it into an
array in a single request:

$r = sqlite_array_query($db, 'SELECT * FROM users');

foreach ($r as $row) {

// do something with $row

However, if you have more than 50 rows and only need sequential access to the data,use
sqlite_unbuffered_query( ):

$r = sqlite_unbuffered_query($db, 'SELECT * FROM users');

while ($row = sqlite_fetch_array($r)) {

// do something with $row

This is the most efficient way to print items in an XML feed or rows in an HTML table because the
data flows directly from SQLite to your PHP script without any overhead tracking behind the scenes.
However, you can't use it with sqlite_num_row( ) or any function that needs to know the "current"
location within the result set.

When you are done with the connection, call sqlite_close( ) to clean up:

sqlite_close($db);

Technically, this is not necessary, since PHP will clean up when your script finishes. However, if you
open many SQLite connections, calling sqlite_close( ) when you're finished reduces memory
usage.

4.1.3 SQLite Versus MySQL


The SQLite function names are similar to the MySQL functions, but not identical.Table 4-1 provides a
side-by-side comparison of the two.
Table 4-1. Comparison of major MySQL and SQLite function names

MySQL SQLite
mysqli_connect( ) sqlite_connect( )

mysqli_close( ) sqlite_close( )
mysqli_query( ) sqlite_query( )

mysqli_fetch_row( ) sqlite_fetch_array( )

mysqli_fetch_assoc( ) sqlite_fetch_array( )
mysqli_num_rows( ) sqlite_num_rows( )

mysqli_insert_id( ) sqlite_last_insert_rowid( )

mysqli_real_escape_string( ) sqlite_escape_string( )

< Day Day Up >


< Day Day Up >

4.2 Alternate SQLite Result Types


SQLite has many different functions for retrieving data. The ones you've already seen are not the
only ones at your disposal, and you can control whether sqlite_fetch_array( ) returns numeric
arrays, associative arrays, or both.

By default, when sqlite_fetch_array( ) returns data, it provides you with an array containing
numeric and associative keys. This is a good thing, because it lets you refer to a column either by its
position in the SELECT or by its name:

$r = sqlite_query($db, 'SELECT username FROM users');

while ($row = sqlite_fetch_array($r)) {

print "user: $row[username]\n"; // this line and...

print "user: $row[0]\n"; // this line are equivalent

This is also a bad thing because it can catch you unawares. For example:

$r = sqlite_query($db, 'SELECT * FROM users');

while ($row = sqlite_fetch_array($r)) {

foreach ($row as $column) {

print "$column\n"; // print each retrieved column

This actually displays every column twice! First it prints the value stored in $row[0], and then it
prints the same value referenced by its column name. If you have a generalized table-printing routine
where you don't know the number of fields in advance, you might fall prey to this bug.

Additionally, if you retrieve a large dataset from SQLite, such as an entire web page or an image,
then each result takes up twice as much memory because there are two copies stashed in the array.

Therefore, SQLite query functions take an optional parameter that controls the results.Pass
SQLITE_ASSOC for only column names, SQLITE_NUM for only column positions, and SQLITE_BOTH for
the combination. These arguments are constants, not strings, so you do not place them in quotation
marks. For example:
// numeric

$row = sqlite_fetch_array($r, SQLITE_NUM);

// associative

$row = sqlite_fetch_array($r, SQLITE_ASSOC);

// both (the default value)

$row = sqlite_fetch_array($r, SQLITE_BOTH);

SQLite returns column names in the same mixed case as you CREATEd them. This is not true of all
databases. Some like to use all uppercase letters; others turn everything into lowercase. When
porting applications from one of these databases to SQLite, use thesqlite.assoc_case
configuration parameter to maintain compatibility without rewriting your code. The default value is0,
for mixed case; changing it to 1 turns the strings in your associative arrays to uppercase, whereas 2
sets them to lowercase. Modifying the column names slows down SQLite slightly, but PHP's
strtolower( ) is significantly worse in this regard.

< Day Day Up >


< Day Day Up >

4.3 Object-Oriented Interface


The SQLite extension allows you to interact with SQLite in an object-oriented manner. SQLite's OO
interface turns your database connection into an object and lets you call methods on it. When using
this interface, there's no need to pass in a database handle to any SQLite functions, because the
object knows what database connection it should use.

Additionally, the SQLite OO interface lets you iterate directly over queries inside aforeach without
needing to call fetch_array( ). PHP will automagically request the appropriate row from SQLite and
then stop the loop when you've read all the rows.

4.3.1 Using the SQLiteDatabase Object


To use the OO interface, instantiate a new SQLiteDatabase object and call methods on it. Example
4-1 uses this interface to connect to the database /www/support/users.db and SELECT all the rows
from the users table.

Example 4-1. Using the SQLite object-oriented interface

$db = new SQLiteDatabase('/www/support/users.db');

// one at a time

$r = $db->query('SELECT * FROM users');

while ($row = $r->fetch( )) {

// do something with $row

// all at once

$r = $db->arrayQuery('SELECT * FROM users');

foreach ($r as $row) {

// do something with $row


}

unset($db);

All procedural SQLite functions are available under the object-oriented interface, but their names are
not identical. For one, you must remove the leading sqlite_ from the function name. Also, names
use studlyCaps instead of underscores.

Additionally, you don't pass in the database link identifier, since that's stored in the object. So,
sqlite_query($db, $sql) becomes $db->query($sql), and so forth.

The major exception to these rules is sqlite_close( ). To end the connection when using the OO
interface, delete the object by using unset( ).

Table 4-2 contains a list of frequently used SQLite functions and their object equivalents.

Table 4-2. SQLite functions

Procedural name Object-oriented name


$db = sqlite_open($table) $db = new SQLiteDatabase($table)

sqlite_close($db) unset($db)

$r = sqlite_query($db, $sql) $r = $db->query($sql)


$r = sqlite_query_array($db, $sql) $r = $db->arrayQuery($sql)

$r = sqlite_query_unbuffered($db, $sql) $r = $db->unbufferedQuery($sql)


sqlite_fetch_array($r) $r->fetch( )

sqlite_fetch_single($r) $r->fetchSingle( )
$safe = sqlite_escape_string($s) $safe = $db->escapeString($s)
$id = sqlite_last_insert_rowid($r) $id = $db->lastInsertRowid($r)

4.3.2 Object Iterators


SQLite takes advantage of a new PHP 5 feature that lets you access rows from your database query
as though they're just elements from an array. This feature is called iteration and is the subject of
Chapter 6.

Don't confuse this with sqlite_array_query( ). SQLite is not prefetching all the rows and storing
them as keys inside an array; instead, upon each loop iteration, it returns a new row as if the row
already lived in your results array:
// one at a time

$r = $db->query('SELECT * FROM users');

foreach ($r as $row) {

// do something with $row

You can also embed the query directly inside the foreach:

// one at a time

foreach ($db->query('SELECT * FROM users') as $row) {

// do something with $row

While this interface hides many of the messy details of database result retrieval, SQLite must still
make the requests and transfer the data from the database. Therefore, this syntax works only in
foreach. You cannot use a for loop or pass $db->query( ) into other array functions, such as
array_map( ).

When iterating over an SQLite result, it's usually best to use theunbuffered_query( ) function or
unbufferedQuery( ) method instead of the simple query( ) method. Since you rarely take
advantage of the additional benefits provided by query( ), unbuffered_query( ) gives you an
efficiency gain at no cost.

// one at a time

$r = $db->unbufferedQuery('SELECT * FROM users');

foreach ($r as $row) {

// do something with $row

< Day Day Up >


< Day Day Up >

4.4 Indexes, Error Handling, and In-Memory Tables


Now that you know the basics, it's time to cover the features needed to create robust applications
using SQLite. Features such as creating primary and other keys, using in-memory tables, and error
handling are all necessary to keep your site up and running in a responsive manner.

4.4.1 Indexes
Adding an index, also called a key, is the easiest way to improve application performance. When
SQLite searches a database without keys, it needs to look at every single row in the table to check
for matches. However, after you apply an index to the search fields, SQLite can often avoid this time-
consuming process. Instead, it consults a specially constructed record that allows SQLite to quickly
look up a field's location within the table.

If you know ahead of time that the data in a particular field in your database is going to be unique
(i.e., each value will appear in only one record for that field), then you should declare the field
UNIQUE in your CREATE TABLE SQL statement. SQLite automatically indexes UNIQUE fields.

CREATE TABLE users (username TEXT UNIQUE, password TEXT);

In a web application, a username field is often unique, whereas a password field is not. When
username is UNIQUE, SQLite creates a key, since SQLite needs to scan the column to protect against
duplicate entries every time you insert a record. This prevents the database from having two users
named rasmus. Also, you often query against fields that are important enough to beUNIQUE:

SELECT * FROM users WHERE username LIKE 'rasmus';

To add an index to any existing SQLite table, issue theCREATE INDEX statement:

CREATE INDEX indexname ON tablename(fieldname);

Here, indexname is the name of the index. It can be anything, but tablename_fieldname_index is a
good way to protect against reusing the same index name. For example:

CREATE INDEX users_username_index ON users(username);

Creating a plain-vanilla INDEX imposes no UNIQUEness constraints on your data. This is helpful
because there are non-unique fields, such as locations or dates, where you still need quick search
capabilities:

SELECT * FROM stores WHERE state LIKE 'New York';


SELECT * FROM purchases WHERE date LIKE '2004-07-22';

You can add a UNIQUE key to a pre-existing table:

CREATE UNIQUE INDEX indexname ON tablename(fieldname);

To remove an index, issue the DROP INDEX command:

DROP INDEX indexname;

Indexes make your database files larger. Other than that, there's usually no harm in keeping an
index around, even if you're not using it.

4.4.2 Primary Keys


A primary key is a special kind of index. When you place primary key status upon a column in your
table, the field serves as a unique identifier for a row. Therefore, if you're interested in gathering
information about a specific row in the database, the best way to retrieve it is by using its primary
key.

A field with primary key status must be an integer. SQLite assigns the number1 to the first row put
into the table, 2 to the second, and so on. If you delete a line from the table, SQLite preserves the
hole in the database and places any new records at the end instead of filling up the empty row.

To get SQLite to automatically create this strictly increasing set of values, first define a column in
your table as an INTEGER PRIMARY KEY. Extending the previous example:

CREATE TABLE users (userid INTEGER PRIMARY KEY,

username TEXT UNIQUE,

password TEXT );

Then, when you add a new row to the table, pass NULL as the value of the primary key:

INSERT INTO users VALUES (NULL, 'rasmus', 'z.8cMpdFbNAPw');

If you want to assign a specific number to a row as its primary key, pass that number instead of
NULL. To find the value of the primary key of the last added row, callsqlite_last_insert_rowid( )
(or lastInsertRowid( ) when using the OO interface). For example:

$db = new SQLiteDatabase('/www/support/users.db');

$sql = "INSERT INTO users VALUES (NULL, '$username', '$password');";


$db->query($sql);

$rowid = $db->lastInsertRowid( );

The $rowid variable holds the primary key assigned to your INSERT.

This method is better than writing a query that retrieves the largest valued key; it's possible that
between inserting your initial row and making this request, another user has altered the table.

4.4.3 Error Handling


Just like the mysqli extension, SQLite error handling differs depending on whether you use the
procedural or object-oriented interface. With the procedural interface, you must check the return
value of each SQLite call and then consult the message in a special SQLite error variable.
Alternatively, the SQLite object tosses an SQLiteException whenever it encounters dragons.

4.4.3.1 Procedural error handling

Here is a good way to structure your procedural code to check for SQLite errors:

$db = sqlite_open($database, 0666, $sqlite_error) or die ($sqlite_error);

if ($r = sqlite_query($db, $sql)) {

// row iteration code here

} else {

die (sqlite_error_string(sqlite_last_error($db)));

There are three different ways to access SQLite errors. When you initially try to connect to an SQLite
database, the third parameter to sqlite_open( ) (in this case $sqlite_error) is a variable passed
by reference. If SQLite cannot open the database, it will return false and store the error message in
$sqlite_error.

The second parameter to sqlite_open( ) is the mode, which is an octal number that describes the
file permissions SQLite uses when creating a new database. Currently, the SQLite extension always
uses a mode of 0666, regardless of what's passed in during sqlite_open( ). In other words, this
value is ignored completely; however, it may be respected in future versions of the extension. This
mode means the database is readable and writable by all users, including the web server.

Once your connection is established, SQLite still returnsfalse upon errors, but it no longer uses
$sqlite_error. Instead, it has a pair of error-reporting functions: sqlite_last_error( ) and
sqlite_error_string( ).

The first function, sqlite_last_error( ), returns the SQLite error code for the most recent error.
Since the error code is a number, it's not very helpful for humans. To convert the number to an
actual error message, pass it to sqlite_error_string( ).

In the previous example, any error triggers a die( ). More user-friendly applications require gentler
error handling. Using error_log( ) in combination with a polite, generic message to users may be
the best solution.

You cannot "save up" error checking while you complete a series of queries. SQLite resets the value
returned by sqlite_last_error( ) after every query, so old error messages will be removed before
you view them. SQLite even resets the message after an error-free query, so a query with an error
followed by valid query leaves you with an error message ofnot an error.

4.4.3.2 Object-oriented error handling

When you use the object-oriented interface to the SQLite extension, you need to process exceptions
or risk a fatal error. Exceptions are a method of error processing that eliminates the need to check
return values of functions. They're described in more detail inChapter 7.

SQLite doesn't always throw exceptions instead of returning NULL. In fact, the opposite it true: it
throws exceptions only from its constructor. Therefore, while you need to catch that single exception,
you still need to rely on traditional error handling for other errors.

Example 4-2 demonstrates this.

Example 4-2. Catching SQLite exceptions

$database = 'sqlite.db';

$sql = 'INVALID SQL';

try {

$db = new SQLiteDatabase($database);

} catch (SQLiteException $error) {

print "Message: ".$error->getMessage( )."\n";

print "File:".$error->getFile( )."\n"; die;

if ($r = $db->query($sql)) {
// row iteration code here

} else {

die (sqlite_error_string($db->lastError( )));

When SQLite has an error, it throws an SQLiteException. After you catch the exception, learn more
about the specific error by calling getMessage( ) and find out which file caused the error with
getFile( ).

For example, if you try to create an SQLite database file in a location where you do not have write
permission, the code inside the catch block prints:

Message: sqlite_factory( ): unable to open database: /sbin/sqlite.db

File: /www/docroot/sqlite.php

When you detect an error outside of the constructor, as in the query( ) method in Example 4-2, use
the lastError( ) method to retrieve the error code. To convert this number to a human-
understandable message, use sqlite_error_string( ). The function sqlite_error_string( ) is
not an object method, because it is static and does not vary between database instances.

4.4.4 In-Memory Tables


For extra-fast access, SQLite supports storing tables in RAM instead of on disk. Unfortunately, these
tables do not persist across requests, so you cannot create them once and then refer to them again
and again; instead, they need to be created each time a page loads. Therefore, these tables are best
used in applications that load in lots of data up front and then make a series of requests, such as a
report generation script.

To tell SQLite to use an in-memory database, pass the token :memory: as your database name:

sqlite_open(':memory:');

sqlite_query('CREATE TABLE...');

Besides the special database name, there's no difference between using in-memory and on-disk
tables. You interact with them using the same set of PHP and SQL commands.

< Day Day Up >


< Day Day Up >

4.5 Transactions
SQLite supports database transactions. Transactions are good for ensuring database consistency, but
they serve a second purpose in SQLite: speed. When a set of queries are grouped together inside a
transaction, SQLite executes them significantly faster than if they were performed individually. The
more queries you throw at SQLite simultaneously, the larger the percentage increase in speed.

When SQLite creates a connection or makes a query, it does a certain amount of setup; likewise,
when it closes a connection or completes a query, it again must perform a sequence of housecleaning
tasks. These duties are relatively expensive, but SQLite needs to do this only once per transaction,
regardless of how many queries are inside the transaction. This translates into a performance
improvement.

However, there's a downside to using transactions in SQLite: when you wrap all your calls into a
transaction, SQLite locks the entire database file, and the locked file cannot be accessed by other
users. (More finely grained locking capabilities are a benefit of using a "real" database instead of
SQLite.) If you're more concerned about overall system responsiveness than with optimizing for a
specific action, benchmark your site to evaluate whether using transactions in this manner is
appropriate in your script.

To signal to SQLite that you want to begin a transaction, use the keyword BEGIN; to end a
transaction, use COMMIT. In PHP, pass these keywords as part of your SQL inside of sqlite_query(
):

$users = array(array('rasmus', 'z.8cMpdFbNAPw'),

array('zeev' , 'asd34.23NNDeq'));

$sql = 'BEGIN;';

foreach ($users as $user) {

$sql .= "INSERT INTO users

VALUES('${user[0]}', '${user[1]}');";

$sql .= 'COMMIT;';

sqlite_query($db, $sql);
The SQL opens with BEGIN, and then PHP iterates through an array, appending a series of INSERTs to
$sql. When the loop is done, COMMIT is appended. SQL statements are separated by semicolons (;).
This lets SQLite know to move from one statement to another. Unlike the MySQL extensions, it is
always acceptable to combine multiple SQL statements in a line, even if you're not within a
transaction.

You can also spread out a transaction over multiple calls tosqlite_query( ) like this:

$users = array(array('rasmus', 'z.8cMpdFbNAPw'),

array('zeev' , 'asd34.23NNDeq'));

sqlite_query($db, 'BEGIN;');

foreach ($users as $user) {

// Assume data is already escaped

$sql = "INSERT INTO users

VALUES('${user[0]}', '${user[1]}');";

sqlite_query($db, $sql);

sqlite_query($db, 'COMMIT;');

It is more efficient to make just a single query; however, spreading your queries out gives you the
opportunity to undo, or roll back, a transaction.

For instance, here's a modification of the previous example that aborts the transaction if an error is
found:

function add_users($db, $users) {

$error = false;

// Start transaction

sqlite_query($db, 'BEGIN;');
// Add each new user one-by-one

foreach ($users as $user) {

$sql = "INSERT INTO users

VALUES('${user[0]}', '${user[1]}');";

sqlite_query($db, $sql);

// Abort if there's an error

if (sqlite_last_error($db)) {

$error = true;

break;

// Revert previous commits on error; otherwise, save

if ($error) {

sqlite_query($db, 'ROLLBACK;');

} else {

sqlite_query($db, 'COMMIT;');

return !$error;

This function does the same loop through $users, but now it checks sqlite_last_error( ) after
every INSERT. If there's an error, the function returns a true value, so $error gets set and you break
out of the loop. When there are no errors, sqlite_last_error( ) returns 0.

Instead of automatically committing the transaction, check $error. If an error is found, reverse the
transaction by executing the ROLLBACK command. Issuing a ROLLBACK instructs SQLite to revert the
status of the database to its condition before BEGIN was sent.
Here is an example that triggers a rollback:

$db = sqlite_open('/www/support/users.db');

$users = array(array('rasmus', 'z.8cMpdFbNAPw'),

array('zeev' , 'asd34.23NNDeq'),

array('rasmus', 'z.8cMpdFbNAPw'));

add_users($db, $users);

Assume the users table requires that each username entry be UNIQUE. Since there are two entries in
the array with a username of rasmus, SQLite issues an error when you attempt to enter the second
rasmus into the table.

You could ignore the error and proceed, but as things currently stand, the entire set of users is
skipped. A more sophisticated example would examine the specific value returned by
sqlite_last_error( ) and take different actions on a case-by-case basis. This would let you skip
over a minor error like this but also let you revert the transaction if a more drastic error occurred.

< Day Day Up >


< Day Day Up >

4.6 User-Defined Functions


In addition to all the built-in SQL functions, such aslower( ) and upper( ), you can extend SQLite
to include functions of your own written in PHP. These are known asuser-defined functions, or UDFs
for short. With a UDF, you embed logic into SQLite and avoid doing it yourself in PHP. This way, you
take advantage of all the features inherent in a database, such as sorting and finding distinct entries.

There are two types of UDFs: standard and aggregate. Standard UDFs are one-to-one: when given a
single row of data, they return a single result. Functions that change case, calculate cryptographic
hashes, and compute the sales tax on an item in a shopping cart are all standard functions. In
contrast, aggregate functions are many-to-one: when using an aggregate function, SQLite passes it
multiple rows and receives only a single value.

Although it is not a UDF, the most popular aggregate function iscount( ), which returns the number
of rows passed to it. Besides count( ), most aggregate functions are related to statistics: finding the
average, standard deviation, or the maximum or minimum value of a set of data points.

4.6.1 Standard Functions


UDFs are good for chopping up strings so you can perform nonstandard collations and groupings. For
example, you want to sort through a list of URLs, maybe from a referrer log file, and create a list of
unique hostnames sorted alphabetically. So, http://www.example.com/directory/index.html and
http://www.example.com/page.html would both map to one entry: http://www.example.com.

To do this in PHP, you need to retrieve all the URLs, process them inside your script, and then sort
them. Plus, somewhere in all that, you need to do the deduping. However, if it weren't for that pesky
URL-conversion process, this could all be done in SQL using theDISTINCT and ORDER BY keywords.

With a UDF like the one shown in Example 4-3, you foist all that hard work back onto SQLite where it
belongs.

Example 4-3. Retrieving unique hostnames using an SQLite UDF

// CREATE table and INSERT URLs

$db = sqlite_open('/www/support/log.db');

$sql = 'CREATE TABLE access_log(url);';

$urls = array('http://www.example.com/directory/index.html',

'http://www.example.com/page.html');
foreach ($urls as $url) {

$sql .= "INSERT INTO access_log VALUES('$url');";

sqlite_query($db, $sql);

// UDF written in PHP

function url2host($url) {

$parts = parse_url($url);

return "$parts[scheme]://$parts[host]";

// Tell SQLite to associate PHP function url2host( ) with the

// SQL function host( ). Say that host( ) will only take 1 argument.

sqlite_create_function($db, 'host', 'url2host', 1);

// Do the query

$r = sqlite_query($db, 'SELECT DISTINCT host(lower(url)) AS clean_host

FROM access_log ORDER BY clean_host;');

// Loop through results

while ($row = sqlite_fetch_array($r)) {

print "$row[clean_host]\n";

}
http://www.example.com

To use a UDF, you first write a regular function in PHP. The function's arguments are what you want
to pass in during the SELECT, and the function should return a single value. The url2host( )
function takes a URL; calls the built-in PHP function parse_url( ) to break the URL into its
component parts; and returns a string containing the scheme, ://, and the host. So,
http://www.example.com/directory/index.html gets broken apart into many pieces. http is
stored into $parts['scheme'] and www.example.com goes into $parts['host'].[1] This creates a
return value of http://www.example.com.

[1] The other portions of the URL are stored in different variables.

The next step is to register url2host( ) with SQLite using sqlite_create_function( ). This
function takes four parameters: the database handle, the name you want the function to be called
inside SQLite, the name of your function written in PHP, and the number of arguments your function
expects. The last parameter is optional, but if you know for certain that your function accepts only a
specific number of parameters, providing this information helps SQLite optimize things behind the
scenes. In this example, the SQL function is host( ), while the PHP function is url2host( ). These
names can be the same; they're different here to make the distinction between them clear.

Now you can use host( ) inside any SQL calls using that database connection. The SQL in Example
4-3 SELECTs host(lower(url)) AS clean_host. This takes the URL stored in the url column,
converts it to lowercase, and calls the UDF host( ).

The function is not permanently registered with the database, and goes away when you close the
database. If you want to use it when you reopen the database, you must reregister it. Also, the
function is registered only for that database; if you open up a new database usingsqlite_connect(
), you need to call sqlite_create_function( ) again.

The returned string is then named AS clean_host; this lets you refer to the results later on in the
SQL query and also access the value in PHP using that name. Since you're still in SQLite, you can
take advantage of this to sort the list using ORDER BY host. This sorts the results in alphabetical
order, starting at a.

Now that's cool, but it's not that cool. What is cool is SQLite's ability to call UDFs in the ORDER BY
clause. If you use the default alphabetical sort, http://php.example.org and http://www.example.org
won't be near each other, because "p" and "w" aren't next to each other in the alphabet. Yet both
hosts are located under the example.org domain, so it makes sense that they should be listed
together. Not surprisingly, another UDF saves the day.

function reverse_host($url) {

list ($scheme, $host) = explode('://', $url);

return join('.',array_reverse(explode('.',$host)));

sqlite_create_function($db, 'reverse', 'reverse_host', 1);


The reverse_host( ) function takes a URL and chops it into two bits, the scheme and host, by
explode( )ing on ://. You can do this because the previous UDF, host( ), has specifically created
strings in this manner. Next, $host is passed through a series of three functions that splits it up into
its component parts, reverses those parts, and then glues them back together. This flips around the
pieces of the host separated by periods, but doesn't actually reverse the text. So,www.example.org
becomes org.example.www and not gro.elpmaxe.www or www.elpmaxe.gro.

This reversed hostname is perfect for sorting. When you alphabetize org.example.www, it nicely sits
next to all its brethren in the .org top-level domain, then sorts by the other hosts inside
example.org, and finally orders the remaining subdomains. And that's exactly what you want.

You then register reverse_host( ) in the exact same way you registered url2string( ), using
sqlite_create_function( ).

Once that's done, you can call reverse( ) inside your SQL query:

$r = sqlite_query($db, 'SELECT DISTINCT host(lower(url)) AS clean_host

FROM access_log ORDER BY reverse(clean_host);');

Given the following list of URLs as input:

http://www.example.com

http://php.example.org

http://www.example.org

you get the following as output:

http://www.example.com

http://php.example.org

http://www.example.org

The URL containing php.example.com has filtered down in the list below www.example.com, even
though php comes before www in the alphabet.

In contrast, Example 4-4 shows what you need to do to implement this in PHP without UDFs.

Example 4-4. Sorting unique hostnames without using SQLite UDFs

function url2host($url) {

$parts = parse_url($url);
return "$parts[scheme]://$parts[host]";

function reverse_host($url) {

list ($scheme, $host) = explode('://', $url);

return join('.',array_reverse(explode('.',$host)));

function host_sort($a, $b) {

$count_a = $GLOBALS['hosts'][$a];

$count_b = $GLOBALS['hosts'][$b];

if ($count_a < $count_b) { return 1; }

if ($count_a > $count_b) { return -1; }

return strcasecmp(reverse_host($a), reverse_host($b));

$hosts = array( );

$r = sqlite_unbuffered_query($db, 'SELECT url FROM access_log');

while ($url = sqlite_fetch_single($r)) {

$host = url2host($url);

$hosts[$host]++ ;

}
uksort($hosts, 'host_sort');

This process breaks down into many steps:

1. Make a database query for urls.

2. Retrieve url into $url using sqlite_fetch_single( ).

3. Convert $url into a host and store it in $host.

4. Place $url as a new element in the $hosts array and increment that element by 1. This tracks
the number of times each URL has appeared.

5. Perform a user-defined key sort on the $hosts array.

The sqlite_fetch_single( ) function returns the first (and in this case only) column from the
result as a string. This allows you to skip the step of saving the result as an array and then extracting
the element, either by using list or as the 0th index.

Doing $hosts[$host]++ is a old trick that allows you to easily count the number of times each key
appears in a list.

Since uksort( ) only passes array keys to the sorting function, host_host( ) is not very elegant,
because it requires using a global variable to determine the number of hits for each element.

Overall, compared to a UDF, this method requires more memory, execution time, and lines of code,
because you're replicating database functionality inside PHP.

4.6.2 User-Defined Aggregate Functions


As discussed earlier, most aggregate functions are statistical functions, such asAVG( ) or STDEV( ).
People usually use aggregate functions to return a single row from their query, but that's not a
requirement. You can use them to link together a set of related rows, to compact your query and
return one row per group.

This extension to the earlier referrer log sorting example shows how to use an aggregate function to
provide the total number of hits per hostname, in addition to everything in the previous section:

SELECT DISTINCT host(lower(url)) AS clean_host, COUNT(*) AS hits

FROM access_log GROUP BY clean_host ORDER BY hits DESC, reverse(clean_host)

The COUNT(*) function sums the total number of rows per host. However, this won't work without
adding the GROUP BY host clause. GROUPing rows allows COUNT(*) to know which sets of entries
belong together. Whenever you have an aggregate function-such as COUNT( ), SUM( ), or any
function that takes a set of rows as input and returns only a single value as its output-use GROUP BY
when you want your query to return multiple rows. (If you're just doing a basicSELECT COUNT(*)
FROM host to find the total number of rows in the table, there's no need for any GROUPing.)
COUNT(*) is aliased to hits, which allows you to refer to it in the ORDER BY clause. Then, to sort the
results first by total hits, from most to least, and then alphabetically within each total, useORDER BY
hits DESC, reverse(host). By putting hits first, you prioritize it over reverse(clean_host) and
the DESC keyword flips the sorting order to descending (the default is ascending).

Using that query, this set of sites:

http://www.example.org

http://www.example.org

http://www.example.com

http://php.example.org

and this PHP code:

while ($row = sqlite_fetch_array($r)) {

print "$row[hits]: $row[clean_host]\n";

gives:

2: http://www.example.org

1: http://www.example.com

1: http://php.example.org

Furthermore, to restrict results to sites with more hits than a specified amount, use aHAVING clause:

SELECT DISTINCT host(lower(url)) AS clean_host, COUNT(*) AS hits

FROM access_log

GROUP BY clean_host

HAVING hits > 1

ORDER BY hits DESC, reverse(clean_host)

You cannot use WHERE here, because WHERE can only operate on data directly from a table. Here the
restriction hits > 1 compares against the result of a GROUP BY, so you need to employ HAVING
instead.

You can define your own aggregate functions for SQLite in PHP. Unlike standard UDFs, you actually
need to define two functions: one that's called for each row and one that's called after all the rows
have been passed in.

The code in Example 4-5 shows how to create a basic SQLite user-defined aggregate function that
calculates the average of a set of numbers.

Example 4-5. Averaging numbers using an SQLite aggregate function

// CREATE table and INSERT numbers

$db = sqlite_open('/www/support/data.db');

$sql = 'CREATE TABLE numbers(number);';

$numbers = array(1, 2, 3, 4, 5);

foreach ($numbers as $n) {

$sql .= "INSERT INTO numbers VALUES($n);";

sqlite_query($db, $sql);

// average_step( ) is called on each row.

function average_step(&$existing_data, $new_data) {

$existing_data['total'] += $new_data;

$existing_data['count']++;

// average_final( ) computes the average and returns it.

function average_final(&$existing_data) {

return $existing_data['total'] / $existing_data['count'];

sqlite_create_aggregate($db, 'average', 'average_step', 'average_final');


$r = sqlite_query($db, 'SELECT average(number) FROM numbers');

$average = sqlite_fetch_single($r);

print $average;

First, you define the two aggregate functions in PHP, just as you do for regular UDFs. However, the
first parameter for both functions is a variable passed by reference that is used to keep track of the
UDF's state. In this example, you need to track both the running sum of the numbers and how many
rows have contributed to this total. That's done in average_step( ).

In average_final( ), the final sum is divided by the number of elements to find the average. This is
the value that's returned by the function and passed back to SQLite (and, eventually, to you).

To formally create an aggregate UDF, use sqlite_create_aggregate( ). It works like


sqlite_create_function( ), but you pass both PHP function names instead of just one.

4.6.3 Binary Data


SQLite is not binary safe by default. Requiring PHP to automatically protect against problems caused
by binary data causes a significant reduction in speed, so you must manually encode and decode data
when it might be anything other than plain text. If your UDFs only operate on text, this isn't a
problem.

Inside a UDF, use sqlite_udf_binary_decode( ) to convert data stored in SQLite into usable
strings in PHP:

function udf_function_encode($encoded_data) {

$data = sqlite_udf_binary_decode($encoded_data);

// rest of the function...

When you're finished, if the return value might also be binary unsafe, re-encode it using
sqlite_udf_binary_encode( ):

function udf_function_decode($encoded_data) {

// rest of the function...

return sqlite_udf_binary_encode($return_value);

}
< Day Day Up >
< Day Day Up >

Chapter 5. XML
PHP 5 has a completely new set of XML extensions that address major problems in PHP 4's XML
extensions. While PHP 4 allows you to manipulate XML, its XML tools are only superficially related.
Each tool covers one part of the XML experience, but they weren't designed to work together, and
PHP 4 support for the more advanced XML features is often patchy. Not so inPHP 5. The new XML
extensions:

Work together as a unified whole

Are standardized on a single XML library: libxml2

Fully comply with W3 specifications

Efficiently process data

Provide you with the right XML tool for your job

Additionally, following the PHP tenet that creating web applications should be easy, there's a new XML
extension that makes it simple to read and alter XML documents. The aptly named SimpleXML
extension allows you to interact with the information in an XML document as though these pieces of
information are arrays and objects, iterating through them with foreach loops and editing them in
place merely by assigning new values to variables.

< Day Day Up >


< Day Day Up >

5.1 XML Extensions in PHP 5


PHP 5 has five major XML extensions. Each one has different features, advantages, and costs:

DOM

The 800-pound gorilla of XML. You can do everything and the <kitchen-sink> with DOM, but
navigating through your documents can be cumbersome.

SAX

PHP's original XML extension. SAX is a streaming, or event-based, parser that uses less
memory than DOM, but frequently requires more complex PHP code.

SimpleXML

A new PHP 5-only extension that excels at parsing RSS files, REST results, and configuration
data. If you know the document's format ahead of time, SimpleXML is the way to go. However,
SimpleXML supports only a subset of the XML specification.

XPath

This extension allows you to query XML documents like you're searching a database, to find the
subset of information that you need and eliminate the unnecessary portions.

XSLT

A way to take XML documents and transform them into HTML or another output format. It uses
XML-based stylesheets to define the templates. XSLT is easily shared among different
applications, but has a quirky syntax.

Most web developers are familiar with XML basics. However, PHP 5 makes it easier to use some less
well-known parts of XML, including XPath and XML Namespaces. XPath is a powerful but underutilized
way to search XML documents and extract information. With XML Namespaces, you can safely
combine pieces of XML from multiple sources into a single XML document and still uniquely identify
every element. If you're not familiar with XML or the advanced bits like XPath and XML Namespaces,
you might want to make a quick detour to the XML introduction in Appendix A.
This chapter starts by introducing DOM and SimpleXML, the two major XML extensions in PHP 5. This
introduction shows how to create, save, and interact with DOM and SimpleXML objects. These two
approaches to working with XML are similar, but they have different aims. DOM is rigorous and works
with any XML, whereas SimpleXML aims to make common XML tasks very easy, at the cost of not
being useful for some types of XML.

Some programmers like DOM because it requires you to be highly disciplined. Additionally, they
believe that when your requests are very explicit, it makes code easier to understand and reduces
the possibility for mistakes.

Other people believe the opposite. They think that DOM is too heavyweight for general XML
processing because even the most basic tasks require multiple levels of dereferencing. To them,
these levels only clutter up code and make it hard to understand exactly what's going on.

In this book, XML examples are given in both DOM and SimpleXML, so you can get a feel for when
you should (or prefer to) use one versus the other.

When porting code, it's nice to learn about new features by reading text describing them and by
scanning a table listing the differences between the old and new versions. However, that's not
practicable here-the major overhaul of the XML extensions means that often the new way to do an
XML processing task is completely different from the old way. It's easier to understand what needs to
be done by examining a few short before-and-after examples. By comparing the two, you can review
your own code and make similar modifications. Once that's done, you can then double back and fill in
the missing parts by looking at the other material.

Therefore, instead of listing all the new methods you can use to manipulate XML, the majority of this
chapter contains code showing how to solve a series of common XML tasks. The examples are as
follows:

Reading XML into a tree

Reading XML from a stream

Creating new XML documents

Searching XML with XPath

Changing XML into HTML or other output formats with XSLT

Validating XML to ensure it conforms to a specification

These sections are all presented in a before-and-after format. First, you see how it was done in PHP
4. Then, you see updated code showing what changes, if any, you need to make to your code for it to
work under PHP 5.

To make things consistent, the examples all use the same XML documents.These documents
represent a basic XML address book. Example 5-1 shows the standard version.

Example 5-1. Example XML address book

<?xml version="1.0"?>
<address-book>

<person id="1">

<!--Rasmus Lerdorf-->

<firstname>Rasmus</firstname>

<lastname>Lerdorf</lastname>

<city>Sunnyvale</city>

<state>CA</state>

<email>[email protected]</email>

</person>

<person id="2">

<!--Zeev Suraski-->

<firstname>Zeev</firstname>

<lastname>Suraski</lastname>

<city>Tel Aviv</city>

<state></state>

<email>[email protected]</email>

</person>

</address-book>

Example 5-2 is a version that uses namespaces.

Example 5-2. Example namespaced XML address book

<?xml version="1.0"?>

<ab:address-book

xmlns:ab="http://www.example.com/address-book/">

<ab:person id="1">
<!--Rasmus Lerdorf-->

<ab:firstname>Rasmus</ab:firstname>

<ab:lastname>Lerdorf</ab:lastname>

<ab:city>Sunnyvale</ab:city>

<ab:state>CA</ab:state>

<ab:email>[email protected]</ab:email>

</ab:person>

<ab:person id="2">

<!--Zeev Suraski-->

<ab:firstname>Zeev</ab:firstname>

<ab:lastname>Suraski</ab:lastname>

<ab:city>Tel Aviv</ab:city>

<ab:state></ab:state>

<ab:email>[email protected]</ab:email>

</ab:person>

</ab:address-book>

< Day Day Up >


< Day Day Up >

5.2 Installing XML and XSLT Support


PHP 5 uses libxml2 and libxslt as the underlying foundation for its XML support. By hooking into
these tools, PHP developers don't need to write their own XML processors, which gives them more
time to work on PHP.

Originally written for the GNOME project, libxml2 is a standalone library that can be used by other
programs, including languages like PHP. Written in C, libxml2 runs on Linux, Windows, Mac OS X,
FreeBSD, and many other operating systems. The official libxml2 web site is at
http://www.xmlsoft.org/.

libxml2 offers several benefits over other XML parsers: speed, licensing, and standards. While other
XML parsers might be faster in certain specialized circumstances,libxml2 is overall one of the
fastest parsing engines available.

libxml2 also implements most XML specifications. In addition to the basic XML standard,libxml2
also supports XML Namespaces, XPath, XPointer, XInclude, XML Catalogs, Relax NG, and XML
Schemas Part 2. Additional standards are being added on a regular basis. libxml2 also integrates
with libxslt to support XSLT.

PHP 5 requires libxml2 2.5.10 or higher. If you are running Unix or Mac OS X, find the version of
libxml2 on your machine by running xml2-config --version:

% xml2-config --version

2.5.10

If this program cannot be found or the version number is lower than 2.5.10, you must install (or
upgrade) libxml2 on your machine. The latest version of libxml2 is available at
http://www.xmlsoft.org/downloads.html.

By default, PHP 5 enables DOM, SAX, and SimpleXML; however, if your copy oflibxml2 is in a
nonstandard location, use --with-libxml-dir=DIR to help PHP during the configuration process.

For instance, on Mac OS X, the fink package manager installs files in /sw by default:

--with-libxml-dir=/sw

PHP 5 also uses libxslt for XSLT processing. Check your version by running xslt-config --
version:

% xslt-config --version
1.0.32

PHP requires a minimum version of libxslt 1.0.18. It does not automatically enable XSLT support;
you must add it yourself:

--with-xsl

You can also set an installation directory for libxslt:

--with-xsl=/sw

Windows distributions of PHP come with libxml2 and libxslt DLLs. However, to enable XSLT
support, uncomment this line from your php.ini file by removing the leading semicolon (;):

;extension=php_xsl.dll

< Day Day Up >


< Day Day Up >

5.3 DOM
DOM is one of PHP 5's two major XML processing extensions. This section introduces DOM, providing
an overview of how it organizes information. It also demonstrates how to turn XML documents into
DOM objects and vice versa.

5.3.1 About DOM


There is only one way to read XML into a tree using PHP 4: DOM. DOM, short for Document Object
Model, is a W3C standard describing a platform- and language-neutral interface for interacting with
XML and other structured documents. DOM then provides a series of utility functions to recurse
through the branches and pick out the nodes and data that you want.

It's easier to parse XML into a tree than to use a streaming parser such as SAX. When you read XML
into a tree, you can move through the document in a way that is similar to navigating PHP data
structures such as multi-dimensional arrays or objects that have subobjects. However, if your
document is large, DOM can use lot of memory.

PHP 5's DOM utilities have undergone a complete rewrite. If you used the DOM functions inPHP 4,
you know that PHP's DOM support has largely been sketchy and incomplete. The original
implementation did not conform at all to the W3C naming conventions, thus partially defeating the
purpose of a language-neutral API. Although PHP 4.3 unveiled an improved and more compliant set
of DOM functions, there are still holes and memory leaks.

On top (or perhaps because) of all this, a large "EXPERIMENTAL" tag had seemingly been
permanently placed upon PHP 4's DOM functions. When the warning "The behavior of this
extension-including the names of its functions and anything else documented about this
extension-may change without notice in a future release of PHP. Use this extension at your own
risk" appears at the top of documentation, it does not engender comfort.

Happily, all that has changed in PHP 5. The new DOM extension not only has updated internals, but
you now interact with it in the standard way, and it has a few new features, such as validation. Still,
the entire DOM specification is quite large and complex, and not all features are available yet. But
what has been implemented is done correctly and is consistent with other languages.

Unfortunately, if you've written any applications that use the old DOM extension, they won't work
with PHP 5. You must update them.

5.3.2 Turning XML Documents into DOM objects


Before you can do anything DOM-related in PHP 5, you need to create a new instance of the DOM
object, called DOMDocument:
$dom = new DOMDocument;

Now you can load XML into $dom. DOM differentiates between XML stored as a string and XML stored
in a file. To read from a string, call loadXML( ); to read from a file, call load( ):

$dom = new DOMDocument;

// read from a string

$dom->loadXML('<string>I am XML</string>');

// read from a file

$dom->load('address-book.xml');

The DOM load( ) method, like all XML input and output methods, actually works for more than just
files. It really works with streams, so it can read from HTTP or write to FTP. SeeChapter 8 for more
information about streams.

If DOM encounters problems reading the XML-for example, the XML is not well-formed, your file
does not exist, or you try to pass in an array instead of a string-DOM emits a warning. In some
cases, such as a failure of the DOM extension to create a new DOM object or safe_mode blocking off
access to the file, it returns false instead.

This example tries to load a string that's invalid XML:

$dom = new DOMDocument;

// read non well-formed XML document

$dom->loadXML('I am not XML');

It causes DOM to give a PHP Warning that begins like this:

PHP Warning: DOMDocument::loadXML( ): Start tag expected, '<' not found

Whitespace is considered significant in XML, so spaces between tags are considered text elements.
For example, there are five elements inside the person element:

<person>

<firstname>Rasmus</firstname>
<lastname>Lerdorf</lastname>

</person>

It looks like there are only two elements, firstname and lastname, but there are actually three
additional text nodes. They're hard to see because they're whitespace. They occur between the
opening person tag and the opening firstname tag, the closing firstname tag and opening
lastname tag, and the closing lastname and closing person tag.

However, removing the whitespace makes the document hard for humans to read. Happily, you can
tell DOM to ignore whitespace:

$dom = new DOMDocument;

// Whitespace is no longer significant

$dom->preserveWhiteSpace = false;

$dom->loadXML('<string>I am XML</string>');

Setting the preserveWhiteSpace attribute to false makes DOM skip over any text nodes that
contain only spaces, tabs, returns, or other whitespace.

5.3.3 DOM Nodes


DOM organizes XML documents into nodes. You can use DOM to retrieve the text stored in a node,
find a node's children, insert another node at that location, and so forth.

Figure 5-1 shows how DOM represents the beginning of the address book.

Figure 5-1. A DOM representation of an XML address book


5.3.3.1 Accessing the root element

The root element of an XML document is stored as the documentElement property of a DOM object:

$dom = new DOMDocument;

$dom->preserveWhiteSpace = false;

$dom->load('address-book.xml');

$root = $dom->documentElement;

The $root variable now holds a pointer to the document root.

5.3.3.2 Navigating through nodes

DOM has a whole set of tree iteration properties that allow you to explicitly move from one element
to another. In PHP 4, these are object methods, but they're object properties in PHP 5.
The easiest way to process all of a node's children is with aforeach upon its childNodes. For
example, to process all the person elements in the address book:

$dom = new DOMDocument;

$dom->preserveWhiteSpace = false;

$dom->load('address-book.xml');

$root = $dom->documentElement;

foreach ($root->childNodes as $person) {

process($person);

The childNodes attribute is not an array, but a DOMNodeList object. The item( ) method allows you
to access individual items, and the length property tells you the number of items in the list.

This code is equivalent to the foreach loop:

$people = $root->childNodes;

for ($i = 0; $i < $people->length; $i++) {

process($people->item($i));

The first element lives in position 0, the second in 1, and so on.

Table 5-1 contains the complete list of properties and what they do.

Table 5-1. DOM iteration properties

PHP 5 property PHP 4 method Description


parentNode parent_node( ) The node above the current node
childNodes child_nodes( ) A list of nodes below the current node
PHP 5 property PHP 4 method Description
firstChild first_child( ) The "first" node below the current node
lastChild last_child( ) The "last" node below the current node
previousSibling previous_sibling( ) The node "before" the current node
nextSibling next_sibling( ) The node "after" the current node

5.3.3.3 Determining node types

libxml2 has 21 different types of nodes. The most frequently encountered types are elements,
attributes, and text. The nodeType method returns a number describing the node.

For instance, the documentElement is always an element:

$root = $dom->documentElement;

print $root->nodeType;

Table 5-2 lists libxml2's node types.

Table 5-2. libxml2's numeric node types

Node type Number

Element 1
Attribute 2

Text 3
CDATA section 4
Entity reference 5

Entity 6
PI (Processing Instruction) 7

Comment 8
Node type Number
Document 9

Document type 10
Document fragment 11

Notation 12

HTML document 13
DTD 14

Element declaration 15
Attribute declaration 16

Entity declaration 17

Namespace declaration 18

XInclude start 19

XInclude end 20

DocBook document 21

5.3.3.4 Accessing text nodes

DOM never makes any assumptions about how your data is organized or what you wish to do with it.
If you have a snippet of XML that looks like this:

<firstname>Rasmus</firstname>

DOM does not assume that your primary interest is the string Rasmus. To DOM, Rasmus is just the
text portion of a child node associated with the node for thefirstname element.

Here's how to access Rasmus:

// load in XML

$rasmus = newDOMDocument;

$rasmus->loadXML('<firstname>Rasmus</firstname>');

// two DOM longhand ways

$rasmus->firstChild->firstChild->nodeValue;

$rasmus->firstChild->firstChild->data;
// the first element of the children method

$rasmus->childNodes->item(0)->nodeValue;

// a DOM shorthand way of saying the same thing

$rasmus->firstChild->nodeValue;

// a PHP 5 shorthand method

// *NOT* portable across DOM implementations

$rasmus->textContent;

// yet another way, because this is the root element

$rasmus->documentElement->nodeValue;

DOM does not couple the element with the text wrapped by its tags. Therefore, you must ask the
node for its first child. This gives you the text node holdingPHP. However, you can't print the node,
because it's an object, not a string. To access the text portion of a text node, you need to grab its
nodeValue.

PHP 5's DOM implementation has a special attribute textContent that's equivalent to firstChild-
>nodeValue. This attribute name is shorter, but it is not portable, because it's not part of the DOM
standard.

5.3.3.5 Accessing element nodes

DOM stores an element's name in the tagName property. This code loops through a person element
from the address book and prints out the names of all the elements and the values of their first
children:

$dom = new DOMDocument;

$dom->preserveWhiteSpace = false;

$dom->load('address-book.xml');

$person = $dom->documentElement->firstChild;
foreach ($person->childNodes as $field) {

if ($field->nodeType = = 1) {

print "$field->tagName: {$field->firstChild->nodeValue}\n";

firstname: Rasmus

lastname: Lerdorf

city: Sunnyvale

state: CA

email: [email protected]

The $person object holds the person node, and its children are the address book fields.

Inside the foreach, you need to check the nodeType to make sure you have an element node. All
elements in libxml2 have a nodeType of 1. Skipping this check processes the comment node
because DOM does not ignore comments.

5.3.4 Turning DOM Objects into XML Documents


To take a DOM document and convert it back into XML, you have two options: save( ) and
saveXML( ). The first method saves a document to a file; the other returns a string representation of
the document, which you can print out or store in a variable.

$dom->save('address-book.xml');

print $dom->saveXML( );

As always, you must have write permission for the directory in which you're saving the file.

If you disable the preserveWhiteSpace attribute, your XML ends up as a single line:

$dom = new DOMDocument;


$dom->preserveWhiteSpace = false;

$dom->load('address-book.xml');

print $dom->saveXML( );

<address-book><person id="1"><!--Rasmus Lerdorf--><firstname>Rasmus</fi

rstname><lastname>Lerdorf</lastname><city>Sunnyvale</city><state>CA</st

ate><email>[email protected]</email></person><person id="2"><!--Zeev Suras

ki--><firstname>Zeev</firstname><lastname>Suraski</lastname><city>Tel A

viv</city><state/><email>[email protected]</email></person></address-book>

To prevent this, set the formatOutput attribute to true:

$dom = new DOMDocument;

$dom->preserveWhiteSpace = false;

$dom->formatOutput = true;

$dom->load('address-book.xml');

print $dom->saveXML( );

<?xml version="1.0"?>

<address-book>

<person id="1">

<!--Rasmus Lerdorf-->

<firstname>Rasmus</firstname>

<lastname>Lerdorf</lastname>

<city>Sunnyvale</city>
<state>CA</state>

<email>[email protected]</email>

</person>

<person id="2">

<!--Zeev Suraski-->

<firstname>Zeev</firstname>

<lastname>Suraski</lastname>

<city>Tel Aviv</city>

<state/>

<email>[email protected]</email>

</person>

</address-book>

Now the elements are indented. However, since libxml2 does not indent comments, those nodes
remain on the left.

< Day Day Up >


< Day Day Up >

5.4 SimpleXML
SimpleXML has been described as "the mostest bestest thing ever." While it's hard to live up to such
grand praise, SimpleXML does do a remarkable job of making it-dare I say-simple to interact with
XML. When you want to read a configuration file written in XML, parse an RSS feed, or process the
result of a REST request, SimpleXML excels at these tasks. It doesn't work well for more complex
XML-related jobs, such as reading a document where you don't know the format ahead of time or
when you need to access processing instructions or comments.

5.4.1 Turning XML Documents into SimpleXML Objects


There are two ways to read XML under SimpleXML. If your XML document is stored as a string in a
PHP variable, use simplexml_load_string( ). If it's in a file, call simplexml_load_file( ) instead.
For instance:

// Variable

$xml = simplexml_load_string('<xml>I am XML</xml>');

// Local file

$people = simplexml_load_file('address-book.xml');

5.4.2 SimpleXML Elements


SimpleXML turns elements into object properties. The text between the tags is assigned to the
property. If more than one element with the same name lives in the same place (such as multiple
<people>s), then they're placed inside a list.

Element attributes become array elements, where the array key is the attribute name and the key's
value is the attribute's value.

Figure 5-2 shows SimpleXML's view of the start of the address book.

Figure 5-2. A SimpleXML representation of an XML address book


To access a single value, reference it directly using object method notation. Here's the same snippet
from the DOM section:

<firstname>Rasmus</firstname>

If you have this in a SimpleXML object, $firstname, here's all you need to do to access Rasmus:

$firstname

SimpleXML assumes that when you have a node that contains only text, you're interested in the text.
Therefore, print $firstname does what you expect it to: it prints Rasmus.

Iteration methods, like foreach, are the best choice for cycling through multiple elements. Code for
this is shown in later examples.

Unlike DOM, attributes are not stored as separate nodes. Instead, attributes are stored as array
elements. For example, this prints out the id attribute for the first person element:

$ab = simplexml_load_file('address-book.xml');

// the id attribute of the first person

print $ab->person['id'] . "\n";

which gives you:

5.4.3 Turning SimpleXML Objects into XML Documents


The asXML( ) method converts a SimpleXML object back into an XML string:

// display the XML

print $ab->asXML( );
The asXML( ) method can save the XML to a file by passing the location of the output file as an
argument:

// save the XML

$ab->asXML('address-book');

This method also works on subnodes. For instance:

$ab = simplexml_load_file('address-book.xml');

print $ab->person->firstname->asXML( );

<firstname>Rasmus</firstname>

< Day Day Up >


< Day Day Up >

5.5 Converting Between SimpleXML and DOM Objects


Using SimpleXML is usually easier than using DOM, but sometimes DOM's comprehensiveness lets
you do something that's either hard or impossible in SimpleXML.

For example, you cannot discover the name of the root element in SimpleXML, but that's no problem
in DOM, as shown in Example 5-3.

Example 5-3. Converting between SimpleXML and DOM objects

$ab = simplexml_load_file('address-book.xml');

// Find the name of the root node

$name = dom_import_simplexml($ab)->tagName;

print $name;

address-book

SimpleXML gobbles up the root element, so there's no way to derive its name using SimpleXML.
However, the dom_import_simplexml( ) function takes a SimpleXML object and converts it to a
DOM document. Under DOM, reference the object's tagName property and you're done.

These conversion features are particularly useful when you're using SimpleXML as a generic XML
document parser, because SimpleXML doesn't really support XML document introspection. In
contrast, DOM excels at this task.

Alternatively, you can perform the reverse transformation, as shown in Example 5-4.

Example 5-4. Converting between DOM and SimpleXML objects

$ab = new DOMDocument;

$ab->load('address-book.xml');
// Find the email address of the first person

$email = simplexml_import_dom($ab)->person->email;

print $email;

[email protected]

Use the simplexml_import_dom( ) function to turn a DOM object into a SimpleXML object.

Don't worry about the performance overhead of these functions. Since SimpleXML and DOM both use
libxml2 as their underlying XML engine, no additional XML parsing occurs when you convert an
object. This makes it efficient to switch between the two interfaces.

< Day Day Up >


< Day Day Up >

5.6 Before and After: Reading XML into a Tree


The most common XML task is reading an XML document and printing out its contents. DOM and
SimpleXML store XML documents in trees. This allows you to easily maneuver though the document
to find information because if you know where your node is located, you can access it directly.

5.6.1 Reading an Address Book


The following programs take an XML document, read it into a tree, and then find a set of nodes. They
all use the example address book from Example 5-1 as their data, search for all the people nodes,
and then print out everyone's first and last name.

The first two versions use DOM, one written in PHP 4 and the other in PHP 5. There is also a
SimpleXML version, which is considerably shorter.

5.6.1.1 PHP 4 and DOM

Example 5-5 demonstrates the PHP 4 DOM extension.

Example 5-5. Reading XML with PHP 4 and DOM

$dom = domxml_open_file('address-book.xml');

foreach ($dom->get_elements_by_tagname('person') as $person) {

$firstname = $person->get_elements_by_tagname('firstname');

$firstname_text = $firstname[0]->first_child( );

$firstname_text_value = $firstname_text->node_value( );

$lastname = $person->get_elements_by_tagname('lastname'));

$lastname_text = $lastname[0]->first_child( );

$lastname_text_value = $lastname_text->node_value( );
print "$firstname_text_value $lastname_text_value\n";

Rasmus Lerdorf

Zeev Suraski

Under PHP 4, DOM method names use underscores ( _ ) to separate words, such as
get_elements_by_tagname( ) and first_child( ).

To find all elements with a given name, use get_elements_by_tagname( ). This returns an array of
elements through which you can iterate. You foreach through each of the <person>s. Within this
loop, there are two steps: retrieving the email element and then printing out the value.

The $person DOM object is just a subtree, or a portion, of the original DOM object, but it is also a
full-featured node of its own. Therefore, you can still call$person-
>get_elements_by_tagname('firstname') to find all firstname elements, just as you earlier called
$dom->get_elements_by_tagname('person') to locate persons. However, in this case, you only
look through the elements in $person instead of the entire XML document.

Since get_elements_by_tagname( ) returns an array of DOM objects but the array contains only
one element, it's referenced with $firstname[0].

You can't directly call a method on a returned object; therefore, it takes two lines to grab an
element's text. You must do $firstname_text = $firstname[0]->first_child( ) and then
$firstname_text_value = $firstname_text->node_value( ).

5.6.1.2 PHP 5 and DOM

DOM in PHP 5 uses objects instead of resources, as shown in Example 5-6.

Example 5-6. Reading XML with PHP 5 and DOM

$dom = newDOMDocument;

$dom->load('address-book.xml');

foreach ($dom->getElementsByTagname('person') as $person) {

$firstname = $person->getElementsByTagname('firstname');

$firstname_text_value = $firstname->item(0)->firstChild->nodeValue;
$lastname = $person->getElementsByTagname('lastname');

$lastname_text_value = $lastname->item(0)->firstChild->nodeValue;

print "$firstname_text_value $lastname_text_value\n";

Rasmus Lerdorf

Zeev Suraski

This is similar to the PHP 4 example. Method names, however, are altered to use the studlyCaps
naming convention. So, get_elements_by_tagname( ) is getElementsByTagname( ).

Such a trivial change seems designed merely to frustrate programmers. However, the DOM standard
uses studlyCaps and so do the DOM methods in every other language, including C, Java, Perl, and
Python. Therefore, the methods were renamed to bring them into line with this convention.

The getElementsByTagname( ) method now returns a DOM node list instead of an array. Therefore,
you access the elements using the item( ) method. For example:

// PHP 4

$firstname[0];

// PHP 5

$firstname->item(0);

Two changes result from PHP 5's improved object model. You no longer need to use a temporary
variable to hold an object before accessing one of its methods. So, to find the
$firstname_text_value, you can write $firstname->item(0)->firstChild->nodeValue. Also, the
firstChild and nodeValue properties are no longer methods, so they don't take ( )s.

Another way to read XML with PHP 5 and DOM is to create a custom class. Since theDOMDocument is
now a PHP object, you can extend it and create your own methods. Example 5-7 creates an
addressBook class that contains a printNames( ) method.

Example 5-7. Reading XML with PHP 5 and DOM by subclassing


DOMDocument
class addressBook extends DOMDocument {

public function printNames( ) {

foreach ($this->getElementsByTagname('person') as $person) {

$firstname = $person->getElementsByTagname('firstname');

$firstname_text_value = $firstname->item(0)->firstChild->nodeValue;

$lastname = $person->getElementsByTagname('lastname');

$lastname_text_value = $lastname->item(0)->firstChild->nodeValue;

print "$firstname_text_value $lastname_text_value\n";

$ab = new addressBook;

$ab->load('address-book.xml');

$ab->printNames( );

Rasmus Lerdorf

Zeev Suraski

The addressBook class has access to all the methods and properties of DOMDocument, so you interact
with it just like a standard DOM object.

You can also create new methods, such as printNames( ). The only difference between the code
inside printNames( ) and the previous example is that you now refer to the DOM object as $this.

This makes it very easy to write a set of DOM manipulation methods that help you navigate through
the object while keeping your ability to invoke a particular DOM method when necessary.
5.6.1.3 PHP 5 and SimpleXML

The SimpleXML version is the shortest, as shown in Example 5-8.

Example 5-8. Reading XML with PHP 5 and SimpleXML

$sx = simplexml_load_file('address-book.xml');

foreach ($sx->person as $person) {

$firstname_text_value = $person->firstname;

$lastname_text_value = $person->lastname;

print "$firstname_text_value $lastname_text_value\n";

Rasmus Lerdorf

Zeev Suraski

When you use SimpleXML, there's no need to call a method to retrieve a set of elements with the
same tag name. Instead, you can directly iterate over them using foreach. Here, the iteration occurs
over $sx->person, which holds all the person nodes.

You can also directly print SimpleXML objects:

foreach ($sx->person as $person) {

print "$person->firstname $person->lastname\n";

Rasmus Lerdorf

Zeev Suraski

PHP interpolates SimpleXML objects inside of quoted strings and retrieves the text stored in them.
5.6.2 Reading an Address Book with Namespaces
Reading namespaced elements is similar to reading global elements, but in addition to the element's
local name, you must also provide the namespace.

The following examples use Example 5-2s revised XML address book, which places everything in a
namespace.

5.6.2.1 PHP 4 and DOM

There is no easy way to get all namespaced elements in PHP 4 if you just use DOM. The best way to
solve this problem is to use DOM in conjunction with XPath. SeeSection 5.7, later in this chapter, for
more details.

5.6.2.2 PHP 5 and DOM

PHP 5 has a special DOM method to retrieve elements within a namespace:


getElementsByTagnameNS( ). To search a namespaced document, replace the calls to
getElementsByTagname( ) with getElementsByTagnameNS( ). Pass the namespace URL as the first
argument and the element's local name as the second argument, as shown inExample 5-9.

Example 5-9. Reading namespaced XML with PHP 5 and DOM

$ab = 'http://www.example.com/address-book/';

$dom = new DOMDocument;

$dom->load('address-book-ns.xml');

foreach ($dom->getElementsByTagnameNS($ab, 'person') as $person) {

$firstname = $person->getElementsByTagnameNS($ab, 'firstname');

$firstname_text_value = $firstname->item(0)->firstChild->nodeValue;

$lastname = $person->getElementsByTagnameNS($ab, 'lastname');

$lastname_text_value = $lastname->item(0)->firstChild->nodeValue;

print "$firstname_text_value $lastname_text_value\n";


}

Rasmus Lerdorf

Zeev Suraski

XML documents use a namespace prefix to handle the problem of bulky URLs. Since DOM methods
use the actual name instead of a prefix, using a variable as a surrogate prefix makes code easier to
read and ensures requests always use the same namespace.

Besides the modified getElementsByTagNameNS( ) method, this code is identical to the non-
namespaced version in Example 5-6.

5.6.2.3 PHP 5 and SimpleXML

SimpleXML assumes that if you're reading an XML document with namespaces, then they were
probably forced upon you by some namespace tyrant and you're wishing that the namespaces would
just disappear. So it does its best to grant your request.

The code to read the namespaced version of the address book is almost identical to the non-
namespaced version in Example 5-7, except that you specify the namespace with the children( )
method. This tells SimpleXML to look for elements inside a namespace. InExample 5-10, this method
is used inside the foreach to find person elements.

Example 5-10. Reading namespaced XML with PHP 5 and SimpleXML

$ab = 'http://www.example.com/address-book/';

$sx = simplexml_load_file('address-book-ns.xml');

foreach ($sx->children($ab)->person as $person) {

$firstname_text_value = $person->firstname;

$lastname_text_value = $person->lastname;

print "$firstname_text_value $lastname_text_value\n";

}
Rasmus Lerdorf

Zeev Suraski

The children( ) method takes a single argument, the namespace URL. The code $sx-
>children($ab)->person is equivalent to $sx->person, except that the call to children($ab)
makes SimpleXML return only person elements inside of the http://www.example.com/address-
book/ namespace.

Once the namespace has been set, SimpleXML remembers it and assumes any further children also
live in that namespace. Therefore, you can access $person->firstname without calling children( )
again, but it is okay to do so:

$sx = simplexml_load_file('address-book-ns.xml');

$ab = 'http://www.example.com/address-book/';

foreach ($sx->children($ab)->person as $person) {

$firstname_text_value = $person->children($ab)->firstname;

$lastname_text_value = $person->children($ab)->lastname;

print "$firstname_text_value $lastname_text_value\n";

< Day Day Up >


< Day Day Up >

5.7 Before and After: Searching XML with XPath


Except for the simplest documents, it's rarely easy to access the data you want one element at a
time. As your XML files become increasingly complex and your parsing desires grow, using XPath is
easier than filtering the data inside a foreach.

In PHP 4 and PHP 5, there is an XPath class that takes a DOM object as its constructor. You can then
search the object and receive DOM nodes in reply. SimpleXML also supports XPath, and it's easier to
use because its integrated into the SimpleXML object.

Whether you're using XPath with DOM or SimpleXML, both extensions support XML Namespaces.
Therefore, you can find elements or attributes that live inside a namespace.

5.7.1 Reading an Address Book


Everything that can be done with the traditional DOM methods can also be done using XPath. The
examples in this section also show how to search the address book.

5.7.1.1 PHP 4 and DOM

To create an XPath query under PHP 4, you must start with a DOM object and pass it off to an XPath
context.

Here's how to retrieve the email addresses of everyone in the address book:

$dom = domxml_open_file('address-book.xml');

$xpath = xpath_new_context($dom);

$emails = $xpath->xpath_eval('/address-book/person/email');

// Can also be:

// $emails = xpath_eval($xpath, '/address-book/person/email');

foreach ($emails->nodeset as $e) {


$tmp = $e->first_child( );

$email = $tmp->node_value( );

// do something with $email

After creating a new DOM object, call xpath_new_context( ) to initialize the XPath context. Query
this context using xpath_eval( ), passing the XPath query as the first parameter (in this example,
it's /people/person/email). This function returns an array of matching DOM nodes, which are
stored in the array's nodeset element. Iterate through nodeset to act upon each node in turn.

By default, xpath_eval( ) operates upon the entire XML document. Search a subsection of the tree
by passing in the subtree as a final parameter to xpath_eval( ). For instance, to gather all the first
and last names of people in the address book, retrieve all the people nodes and query each node
individually, as in Example 5-11.

Example 5-11. Using XPath with PHP 4 and DOM

$dom = domxml_open_file('address-book.xml');

$xpath = xpath_new_context($dom);

$person = $xpath->xpath_eval('/address-book/person');

foreach ($person->nodeset as $p) {

$fn = $xpath->xpath_eval('firstname', $p);

$tmp = $fn->nodeset[0]->first_child;

$firstname = $tmp->node_value( );

$ln = $xpath->xpath_eval('lastname', $p);

$tmp = $ln->nodeset[0]->first_child( );

$lastname = $tmp->node_value( );

print "$firstname $lastname\n";


}

Rasmus Lerdorf

Zeev Suraski

Inside the foreach, call xpath_eval( ) to retrieve the firstname and lastname nodes. Now, in
addition to the XPath query, also pass $people to the method. This makes the search local to the
node.

5.7.1.2 PHP 5 and DOM

DOM supports XPath queries, but again you do not perform the query directly on the DOM object
itself. In keeping with PHP 5's superior OO capabilities, instead of the using thexpath_new_context(
) function, you create a DOMXPath object:

$dom = newDOMDocument;

$dom->load('address-book.xml');

$xpath = new DOMXPath($dom);

$email = $xpath->query('/address-book/person/email');

Instantiate DOMXPath by passing in a DOMDocument to the constructor. To execute the XPath query,
call query( ) with the query text as your argument. This returns an iterable DOM node list of
matching nodes. The email example is now:

$dom = newDOMDocument;

$dom->load('address-book.xml');

$xpath = new DOMXPath($dom);

$emails = $xpath->query('/address-book/person/email');

foreach ($emails as $e) {

$email = $e->firstChild->nodeValue;

// do something with $email

}
The fundamental logic is the same for this example and the PHP 4 DOM version; however, this code is
cleaner. The XPath querying method no longer places the node list in anodeset array element, so
you iterate directly over the returned list.

Also, the PHP 4 example requires a temporary variable, $tmp, to hold $e->firstChild. With PHP 5,
you can access $e->firstChild->nodeValue directly.

The more complex example, where you retrieve firstname and lastname, is significantly shorter
than the PHP 4 version, as shown in Example 5-12.

Example 5-12. Using XPath with PHP 5 and DOM

$dom = newDOMDocument;

$dom->load('address-book.xml');

$xpath = new domXPath($dom);

$person = $xpath->query('/address-book/person');

foreach ($person as $p) {

$fn = $xpath->query('firstname', $p);

$firstname = $fn->item(0)->firstChild->nodeValue;

$ln = $xpath->query('lastname', $p);

$lastname = $ln->item(0)->firstChild->nodeValue;

print "$firstname $lastname\n";

Rasmus Lerdorf

Zeev Suraski

Again, the temporary variable is eliminated, as well as the need to referencenodeset. However, the
syntax to restrict query to a subset of the tree is the same as that in the email example-you still
pass the subtree in as a second parameter.
5.7.1.3 PHP 5 and SimpleXML

In contrast to DOM, all SimpleXML objects have an integrated query( ) method. Calling this method
queries the current object using XPath and returns a SimpleXML object containing the matching
nodes, so you don't need to instantiate another object to use XPath. The method's one argument is
your XPath query.

To find all the matching email addresses in Example 5-1s sample address book:

$s = simplexml_load_file('address-book.xml');

$emails = $s->xpath('/address-book/person/email');

foreach ($emails as $email) {

// do something with $email

This is shorter because there's no need to dereference the firstNode or to take the nodeValue.

SimpleXML handles the more complicated example, too. Sincexpath( ) returns SimpleXML objects,
you can query them directly, as in Example 5-13.

Example 5-13. Using XPath with PHP 5 and SimpleXML

$s = simplexml_load_file('address-book.xml');

$people = $s->xpath('/address-book/person');

foreach($people as $p) {

list($firstname) = $p->xpath('firstname');

list($lastname) = $p->xpath('lastname');

print "$firstname $lastname\n";

Rasmus Lerdorf
Zeev Suraski

Since the inner XPath queries return only one element, use list to grab it from the array.

5.7.2 Reading an Address Book with Namespaces


When your elements and attributes live inside an XML namespace, you must indicate this in your
XPath request. To refer to a namespaced element in XPath, append a namespace prefix and colon to
the local name.

The revised XPath query to find email addresses is as follows:

/ab:address-book/ab:person/ab:email

Of course, this assumes that your XPath processor knows to map the prefix ab to the namespace
http://www.example.com/address-book/. Sometimes this is done automatically; other times, you
must do this in your program.

In PHP, every XPath extension provides a function that lets you associate prefixes and namespace
URLs. In PHP 4, you have to call this function to register all namespaces. In PHP 5, both DOM and
SimpleXML will automatically register any namespaces used in the document with their prefix.

Of course, you are always free to register another prefix for a namespace. This is a good idea if you
don't produce all the XML documents you're consuming. If your data provider alters their namespace
prefix, their document is still valid because a validator ignores prefixes and only examines the actual
namespace URL. However, if you're relying on a specific prefix in your code, your application will
break.

Regardless of whether you're using PHP 4 or PHP 5, to use XPath to find elements living in the default
namespace, you must always manually assign a prefix to the namespace inside your program and
then use that prefix in your XPath query. This is not a limitation of PHP, but a design "feature" of the
XPath specification.

Example 5-14 modifies Example 5-2 to use a default namespace instead of the prefix ab.

Example 5-14. Example default namespaced XML address book

<address-book

xmlns="http://www.example.com/address-book/">

<person id="1">

<!--Rasmus Lerdorf-->

<firstname>Rasmus</firstname>
<lastname>Lerdorf</lastname>

<city>Sunnyvale</city>

<state>CA</state>

<email>[email protected]</email>

</person>

<person id="2">

<!--Zeev Suraski-->

<firstname>Zeev</firstname>

<lastname>Suraski</lastname>

<city>Tel Aviv</city>

<state></state>

<email>[email protected]</email>

</person>

</address-book>

If you encounter an XML document like this, you cannot use the following XPath query:

/address-book/person/email

This query searches for non-namespaced elements, and your elements live in a namespace, even if
they do not explicitly indicate this. You should create a prefix to use with the
http://www.example.com/address-book/ namespace and use that in your queries. The code for
this is shown in the following examples.

5.7.2.1 PHP 4 and DOM

Example 5-15 shows how to find first and last names in a namespaced XML document in PHP 4.

Example 5-15. Using XPath with PHP 4, DOM, and namespaces

$dom = domxml_open_file('address-book-ns.xml');
$xpath = new domXPath($dom);

$xpath->xpath_register_ns('ab', 'http://www.example.com/address-book/');

$person = $xpath->query('/ab:address-book/ab:person');

foreach ($person as $p) {

$fn = $xpath->query('ab:firstname', $p);

$firstname = $fn[0]->firstChild->nodeValue;

$ln = $xpath->query('ab:lastname', $p);

$lastname = $ln[0]->firstChild->nodeValue;

print "$firstname $lastname\n";

Rasmus Lerdorf

Zeev Suraski

There are two differences between this version and the non-namespaced one. First, there's a call to
xpath_register_ns( ) to associate ab with the namespace. Second, wherever there are XPath
queries, all elements are prefixed with ab: (for example, /ab:address-book/ab:person).

5.7.2.2 PHP 5 and DOM

In PHP 5, there are few differences between Example 5-16 and the non-namespaced version because
the DOMXPath object automatically registers ab as a prefix.

Example 5-16. Using XPath with PHP 5, DOM, and namespaces

$dom = newDOMDocument;

$dom->load('address-book-ns.xml');

$xpath = new DOMXPath($dom);


$person = $xpath->query('/ab:address-book/ab:person');

foreach ($person as $p) {

$fn = $xpath->query('ab:firstname', $p);

$firstname = $fn->item(0)->firstChild->nodeValue;

$ln = $xpath->query('ab:lastname', $p);

$lastname = $ln->item(0)->firstChild->nodeValue;

print "$firstname $lastname\n";

Rasmus Lerdorf

Zeev Suraski

As you can see, the changes occur only in the XPath query syntax.

To register a namespace, call the DOMXPath object's registerNamespace( ) method. For example,
this registers ab:

$xpath->registerNamespace('ab', 'http://www.example.com/address-book/');

5.7.2.3 PHP 5 and SimpleXML

SimpleXML also registers namespaces for you, so it too requires minimal modifications, as shown in
Example 5-17.

Example 5-17. Using XPath with PHP 5, SimpleXML, and namespaces

$s = simplexml_load_file('address-book-ns.xml');

$people = $s->xpath('/ab:address-book/ab:person');
foreach($people as $p) {

list($firstname) = $p->xpath('ab:firstname');

list($lastname) = $p->xpath('ab:lastname');

print "$firstname $lastname\n";

Rasmus Lerdorf

Zeev Suraski

You cannot currently register namespaces using SimpleXML. This means that the only way to access
elements in the default namespace is to invoke a cumbersome XPath expression.

Example 5-18 shows how to query the address book when the elements live inside a default
namespace, as in Example 5-14.

Example 5-18. Using XPath with PHP 5, SimpleXML, and default


namespaces

$s = simplexml_load_file('address-book-ns.xml');

$ab = 'http://www.example.com/address-book/';

$people = $s->xpath("/*[local-name( )='address-book' and

namespace-uri( )='$ab']

/*[local-name( )='person' and

namespace-uri( )='$ab']");

foreach($people as $p) {

list($firstname) = $p->xpath("*[local-name( )='firstname' and

namespace-uri( )='$ab']");

list($lastname) = $p->xpath("*[local-name( )='lastname' and


namespace-uri( )='$ab']");

print "$firstname $lastname\n";

Rasmus Lerdorf

Zeev Suraski

This complex XPath expression uses the local-name( ) and namespace-uri( ) XPath functions to
search for nodes based on the namespace URL, instead of the more concise syntax that uses
namespace prefixes.

For more on XPath, see Section A.6.

< Day Day Up >


< Day Day Up >

5.8 Reading XML as Events with SAX


SAX is the original XML extension available with PHP. It's also the best supported, sinceexpat has
been bundled with PHP since the release of PHP 4. PHP's SAX support in PHP 5 and PHP 4 is the
same. The only difference is a behind-the-scenes change.

Since PHP 5 unbundles expat in favor of libxml2, a compatibility layer maps SAX calls from one
parser to the other. Therefore, your SAX applications should work exactly as they did underexpat.
Theory, however, doesn't always equal reality. There are a few differences that can slip you up.

Here's a list of the major incompatibilities:

Namespace parsing with xml_parser_create_ns( )

The xml_parser_create_ns( ) function, which is a namespace-aware version of


xml_parser_create( ) has problems with default namespaces under old versions of libxml2.
Therefore, this function is disabled unless you build PHP usinglibxml2 Version 2.6 or greater.

Fallback handling with xml_set_default_handler( )

With expat, all events that lack a handler are processed by the default handler. Withlibxml2,
you must define a specific handler for each event. The default handler only handles comments
and internal entities, such as &amp;.

External entity handling with xml_set_external_entity_ref_handler( )

This function works under libxml2. Under expat, the default handler captures external entities
like <!ENTITY rasmus SYSTEM "rasmus.ent">.

< Day Day Up >


< Day Day Up >

5.9 Before and After: Creating New XML Documents


If you want to create a new XML document, you have two choices. Since XML is text, you can always print it you
using print . The other option is to use DOM.

With DOM you assemble an entire XML document by calling methods in PHP. When you're done, tell DOM to conv
your DOM object into XML.

At first, it is much easier to create XML yourself and avoid DOM. Just as extracting information from a DOM objec
requires multiple steps, adding new nodes to a DOM tree isn't easy. However, using DOM does have some benefi

Your XML is always well-formed and without syntax errors.

DOM correctly outputs your document in any one of many different character encodings.

You can easily validate your object against a schema.

SimpleXML does not allow you to create XML documents.

5.9.1 Creating an Address Book


The code examples in this section create the address book from Example 5-1and populate it with the first entry.

5.9.1.1 PHP 4 and DOM

Example 5-19 shows how to create an address book in PHP 4.

Example 5-19. Creating XML with PHP 4 and DOM

// create a new document

$dom = domxml_new_doc('1.0');

$people = $dom->append_child($dom->create_element('address-book'));

$person = $people->append_child($dom->create_element('person'));

$person->set_attribute('id', 1);

$person->append_child($dom->create_comment('Rasmus Lerdorf'));
$e = $person->append_child($dom->create_element('firstname'));

$e->append_child($dom->create_text_node('Rasmus'));

$e = $person->append_child($dom->create_element('lastname'));

$e->append_child($dom->create_text_node('Lerdorf'));

$e = $person->append_child($dom->create_element('city'));

$e->append_child($dom->create_text_node('Sunnyvale'));

$e = $person->append_child($dom->create_element('state'));

$e->append_child($dom->create_text_node('CA'));

$e = $person->append_child($dom->create_element('email'));

$e->append_child($dom->create_text_node('[email protected]'));

echo $dom->dump_mem(true);

This prints:

<?xml version="1.0"?>

<address-book>

<person id="1">

<!--Rasmus Lerdorf-->

<firstname>Rasmus</firstname>

<lastname>Lerdorf</lastname>

<city>Sunnyvale</city>
<state>CA</state>

<email>[email protected]</email>

</person>

</address-book>

The create_element( ) method creates a new XML element. However, it's not actually attached to the DOM tree
element is just floating out in the ether, waiting to be placed into a document. The next call,append_child( ) ,
attaches the node to the tree under the object. Similarly, the comment is created usingcreate_comment( ) and
then added to the node.

Therefore, adding new text elements is largely a repetitive process ofcreate and append . For each entry in the
address book, both an element and a text node must be create d. Those objects are then passed to append_chil
) , to insert them into the DOM tree.

5.9.1.2 PHP 5 and DOM

In PHP 5, DOM objects are real PHP objects. This means there are now two ways to interact with DOM. The first w
similar to PHP 4's: you create new nodes by calling methods on a DOM object. However, you can also instantiate
nodes as actual PHP objects.

Example 5-20 shows how to make the address book using the PHP 5 DOM syntax.

Example 5-20. Creating XML with PHP 5 and DOM

$dom = new DOMDocument('1.0', 'utf-8');

$people = $dom->appendChild($dom->createElement('address-book'));

$person = $people->appendChild($dom->createElement('person'));

$person->setAttribute('id', 1);

$person->appendChild($dom->createComment('Rasmus Lerdorf'));

$person->appendChild($dom->createElement('firstname', 'Rasmus'));

$person->appendChild($dom->createElement('lastname', 'Lerdorf'));

$person->appendChild($dom->createElement('city', 'Sunnyvale'));

$person->appendChild($dom->createElement('state', 'CA'));

$person->appendChild($dom->createElement('email', '[email protected]'));
$dom->formatOutput = true; // indent elements

print $dom->saveXML( );

<?xml version="1.0" encoding="utf-8"?>

<address-book>

<person id="1">

<!--Rasmus Lerdorf-->

<firstname>Rasmus</firstname>

<lastname>Lerdorf</lastname>

<city>Sunnyvale</city>

<state>CA</state>

<email>[email protected]</email>

</person>

</address-book>

The DOMDocument constructor takes two optional arguments: the XML version number and the document encodin
the time of this writing, most people use XML 1.0 (the W3C released 1.1 on February 4, 2004), and so the defau
value for this parameter is 1.0 . Typical encoding types include UTF-8, UTF-16, and ISO-8859-1 (aka ISO-Latin-1
you aren't restricted to these three. You can't set the encoding of a DOM document in PHP 4.

Other methods act like they do in PHP 4, but they do not have underscores in their names.

The createElement( ) method has one new feature. If you pass in only one argument, it makes an element with
name. This behavior is similar to how create_element( ) works in PHP 4. However, if you pass in two argument
second parameter makes DOMElement create a text element with that string and append it as a child.

DOM supports the full set of XML items: elements, attributes, text nodes, comments, CDATA sections, PIs, and s
forth. It also lets you create document fragments, which are identical to DOM objects but don't require the XML s
inside of them to be well-formed. XML, for instance, limits a document to a single root node, but a document frag
can have multiple roots. You might want this setup if you're planning on appending this fragment inside of a XML
document, where its layout is valid.

Creating a DOM node of another type, such as a comment, is identical to the earlier example. You just substitute
createComment( ) for createElement( ) . Table 5-3 lists the all these functions, what they are called in PHP 4,
what they look like in an XML document.
Table 5-3. DOM node-creation functions

Description PHP 4 PHP 5 method PHP 5 class

Element create_element( name ) createElement( name ) new DOMElement( name )

create_attribute( name , value createAttribute( name , value new DOMAttribute( name


Attribute
) ) value )

Text create_text_node( text ) createTextNode( text ) new DOMText( text )

Comment create_comment( comment ) createComment( comment ) new DOMComment( commen

new DOMCDATASection( CD
CDATA create_cdata_section( CDATA ) createCDATASection( CDATA )
)

new
Processing create_processing_instruction( createProcessingInstruction(
DOMProcessingInstructi
instruction target , pi ) target [ , pi ])
target [ , pi ])

Document
N/A createDocumentFragment( ) new DOMDocumentFragmen
fragment

CDATA sections are similar to text nodes, but inside a CDATA section you don't need to escape entities and other
markup characters, because they're treated as literal text.

The object-oriented alternative looks like Example 5-21 .

Example 5-21. Creating XML with PHP 5 and DOM using OO

$dom = new DOMDocument('1.0', 'utf-8');

$people = $dom->appendChild(new DOMElement('address-book'));

$person = $people->appendChild(new DOMElement('person'));

$person->appendChild(new DOMAttr('id', 1));

$person->appendChild(new DOMComment('Rasmus Lerdorf'));

$person->appendChild(new DOMElement('firstname', 'Rasmus'));

$person->appendChild(new DOMElement('lastname', 'Lerdorf'));

$person->appendChild(new DOMElement('city', 'Sunnyvale'));

$person->appendChild(new DOMElement('state', 'CA'));


$person->appendChild(new DOMElement('email', '[email protected]'));

$dom->formatOutput = true; // indent elements

print $dom->saveXML( );

In this version, it's easier to see when you're creating an object and what its type is. TheappendChild( ) method
now used even to add new attributes. Instead of calling setAttribute( ) , you now append a new DOMAttr objec

< Day Day Up >


< Day Day Up >

5.10 Before and After: Transforming XML with XSLT


PHP 4 has a separate XSLT extension that relies on the Sablotron XSLT parsing library. In PHP 5, that's replaced
libxslt has replaced Sablotron as the processor of choice.

5.10.1 PHP 4
In PHP 4, the XSLT extension uses resources instead of objects:

$xml = 'data.xml';

$xsl = 'stylesheet.xsl';

$xslt = xslt_create( );

$results = xslt_process($xslt, $xml, $xsl);

if (!$results) {

error_log("XSLT Error: #".xslt_errno($xslt).": ".xslt_error($xslt));

xslt_free($xslt);

You pass xslt_process( ) the filenames of your XML and XSLT documents, and it loads the XML from disk. You
syntax), but not from a DOM object.

5.10.2 PHP 5
Using XSLT in PHP 5 involves two main steps: preparing the XSLT object and then triggering the actual transform

To begin, load in the stylesheet using DOM. Then, instantiate a newXSLTProcessor object, and import the XSLT d
importStylesheet( ) method.

// Load XSL template

$xsl = newDOMDocument;
$xsl->load('stylesheet.xsl');

// Create new XSLTProcessor

$xslt = new XSLTProcessor( );

// Load stylesheet

$xslt->importStylesheet($xsl);

Now the transformer is up and running. You can transform any DOM object in one of three ways: into a string, in

// Load XML input file

$xml = newDOMDocument;

$xml->load('data.xml');

// Transform to string

$results = $xslt->transformToXML($xml);

// Transform to a file

$results = $xslt->transformToURI($xml, 'results.txt');

// Transform to DOM object

$results = $xslt->transformToDoc($xml);

When you call transformToXML( ) or transformToDoc( ) , the extension returns the result string or object. In c
written to the file, not the actual document.

These methods return false when they fail, so to accurately check for failure, write:

if (false = == ($results = $xslt->transformToXML($xml))) {

// an error occurred

}
Using = = = prevents a return value of 0 from being confused with an actual error.

5.10.3 Setting Parameters


You can pass data from PHP into your XSLT stylesheet with the setParameter( ) method. This allows you to do t

For example, the program in Example 5-22 allows you to find people based on their city.

Example 5-22. Setting XSLT parameters from PHP

// This could also come from $_GET['city'];

$city = 'Tel Aviv';

$dom = new DOMDocument

$$dom->load('address-book.xml');

$xsl = new DOMDocument

$xsl->load('stylesheet.xsl');

$xslt = new XSLTProcessor( );

$xslt->importStylesheet($xsl);

$xslt->setParameter(NULL, 'city', $city);

print $xslt->transformToXML($dom);

The program uses the following stylesheet:

<?xml version="1.0" ?>

<xsl:stylesheet version="1.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="@*|node( )">

<xsl:copy>

<xsl:apply-templates select="@*|node( )"/>


</xsl:copy>

</xsl:template>

<xsl:template match="/address-book/person">

<xsl:if test="city=$city">

<xsl:copy>

<xsl:apply-templates select="@*|node( )"/>

</xsl:copy>

</xsl:if>

</xsl:template>

</xsl:stylesheet>

The program and stylesheet combine to produce the following results:

<?xml version="1.0"?>

<address-book>

<person id="2">

<!--Zeev Suraski-->

<firstname>Zeev</firstname>

<lastname>Suraski</lastname>

<city>Tel Aviv</city>

<state/>

<email>[email protected]</email>

</person>

</address-book>
The PHP script does a standard XSLT transformation, except that it calls$xslt->setParameter(NULL, 'city',
second is the parameter's name, and the third is the parameter's value.

Here, the value stored in the PHP variable $city -in this case, Tel Aviv -is assigned to the XSLT parameter ci
placing the following in an XSLT file:

<xsl:param name="city">Tel Aviv</xsl:param>

You usually access a parameter inside a stylesheet like you do a PHP variable, by placing a dollar sign $
( ) in front
matches /address-book/person nodes.

Inside the template, you test whether city=$city ; in other words, is the city child of the current node equal to
children are copied along; otherwise, the records are eliminated.

In this case, city is set to Tel Aviv , so Rasmus's record is removed and Zeev's remains.

5.10.4 Calling PHP Functions from Stylesheets


XSLT parameters are great when you need to communicate from PHP to XSLT. However, they're not very useful
extract information from the stylesheet during the transformation. Ideally, you could call PHP functions from a st

PHP 4 solves this using a Sablotron feature known as scheme handlers. Scheme handlers aren't available in PHP 5
However, there's a new method that duplicates this functionality: registerPHPFunctions( ) . Here's how it's en

$xslt = new XSLTProcessor( );

$xslt->registerPHPFunctions( );

This allows you to call any PHP function from your stylesheets. It's not available by default, because it presents a
people.

Both built-in and user-defined functions work. Inside your stylesheet, you must define a namespace and call thef

<?xml version="1.0" ?>

<xsl:stylesheet version="1.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

xmlns:php="http://php.net/xsl"

xsl:extension-element-prefixes="php">

<xsl:template match="/">

<xsl:value-of select="php:function('strftime', '%c')" />

</xsl:template>
</xsl:stylesheet>

At the top of the stylesheet, define the namespace for PHP: http://php.net/xsl . This example sets the namesp
value to php so XSLT knows these are functions.

To call a PHP function, reference php:function( ) . The first parameter is the function name; additional parame
is strftime and the one argument is %c . This causes strftime to return the current date and time.

Example 5-23 uses this stylesheet, stored as stylesheet.xsl , to process a single-element XML document.

Example 5-23. Transforming XML with PHP 5, XSLT, and PHP functions

$dom = new DOMDocument;

$dom->loadXML('<blank/>');

$xsl = new DOMDocument

$xsl->load('stylesheet.xsl');

$xslt = new XSLTProcessor( );

$xslt->importStylesheet($xsl);

$xslt->registerPHPFunctions( );

print $xslt->transformToXML($dom);

Mon Jul 22 19:10:21 2004

This works like standard XSLT processing, but there's an additional call toregisterPHPFunctions( ) to activate

You can also return DOM objects. This example takes the XML address book and mangles all the email addresses
the document is left untouched:

function mangle_email($nodes) {

return preg_replace('/([^@\s]+)@([-a-z0-9]+\.)+[a-z]{2,}/is',

'$1@...',

$nodes[0]->nodeValue);
}

$dom = new DOMDocument;

$dom->load('address-book.xml');

$xsl = new DOMDocument

$xsl->load('stylesheet.xsl');

$xslt = new XSLTProcessor( );

$xslt->importStylesheet($xsl);

$xslt->registerPhpFunctions( );

print $xslt->transformToXML($dom);

Inside your stylesheet, create a special template for /address-book/person/email elements:

<?xml version="1.0" ?>

<xsl:stylesheet version="1.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

xmlns:php="http://php.net/xsl"

xsl:extension-element-prefixes="php">

<xsl:template match="@*|node( )">

<xsl:copy>

<xsl:apply-templates select="@*|node( )"/>

</xsl:copy>

</xsl:template>

<xsl:template match="/address-book/person/email">
<xsl:copy>

<xsl:value-of select="php:function('mangle_email', node( ))" />

</xsl:copy>

</xsl:template>

</xsl:stylesheet>

The first template ensures that the elements aren't modified, while the second passes the current node to PHP fo
function is passed the current node, represented in XPath as node( ) , instead of a string. Be sure not to place th
node( ) .

Nodes becomes DOM objects inside PHP and always arrive in an array. In this case,mangle_email( ) knows ther
email address is located in $nodes[0]->nodeValue .

When you know that you're only interested in the text portion of a node, use thefunctionString( ) function. Th
omit the array access and nodeValue dereference:

function mangle_email($email) {

return preg_replace('/([^@\s]+)@([-a-z0-9]+\.)+[a-z]{2,}/is',

'$1@...',

$email);

// all other code is the same as before

The new stylesheet template for /address-book/person/email is:

<xsl:template match="/address-book/person/email">

<xsl:copy>

<xsl:value-of

select="php:functionString('mangle_email', node( ))" />

</xsl:copy>

</xsl:template>

The mangle_email( ) function now processes $email instead of $nodes[0]->nodeValue because the template n
The function( ) and functionString( ) methods are incredibly useful, but using them undermines the premis
call PHP from XSLT, you cannot easily reuse your stylesheets in projects that use Java, Perl, and other languages
the trade-off between convenience and portability before using this feature.

< Day Day Up >


< Day Day Up >

5.11 Validating Against a Schema


Schemas are a way to define a specification for your XML documents. In PHP 4, there is no built-in
way to validate an XML document against any type of schema. The PEAR XML_DTD package (available
at http://pear.php.net/package/XML_DTD) provides a way to validate XML files against a Document
Type Definition (DTD). However, because it uses SAX, it is not easy to combine DTD validation with
DOM.

PHP 5 allows you to validate files against DTDs, XML Schema, and RelaxNG schema. The DOM
extension supports all three types, while SimpleXML provides only an XML Schema validator.

5.11.1 PHP 5 and DOM


Validating any file using DOM is a similar process, regardless of the underlying schema format. To
validate, call a validation method on a DOM object. It returns true if the file passes. If there's an
error, it returns false and prints a message to the error log. There is no method for "capturing" the
error message.

$file = 'address-book.xml';

$schema = 'address-book.xsd';

$ab = new DOMDocument

$ab->load($file);

if ($ab->schemaValidate($schema)) {

print "$file is valid.\n";

} else {

print "$file is invalid.\n";

If the schema is stored in a string, use DOMDocument::schemaValidateSource( ) instead of


schemaValidate( ).

Table 5-4 lists all the validation methods.


Table 5-4. DOM schema validation methods

Method name Schema type Data location


schemaValidate XML Schema File
schemaValidateSource XML Schema String
relaxNGValidate RelaxNG File
relaxNGValidateSource RelaxNG String
validate DTD N/A

All of the validation methods behave in a similar manner, so you only need to switch the method
name in the previous example to switch to a different validation scheme.

Both XML Schema and RelaxNG support validation against files and strings. You can validate a DOM
object only against the DTD defined at the top of the XML document.

< Day Day Up >


< Day Day Up >

Chapter 6. Iterators and SPL


Iteration is a key component of PHP programming. Whenever you loop through the elements of an
array, the rows in a database, the files in a directory, or the lines of an HTML table, that's iteration.

Iteration takes many forms in PHP, but the most popular two are foreach and while loops. For
example:

foreach ($array as $key => $value) {

// iterate through array elements

reset($array);

while (list($key, $value) = each($array)) {

// iterate through array elements

Although both of these constructs solve the same problem, foreach is shorter and easier. Iteration
using a while loop can also introduce subtle bugs into your code, like in this example:

// $path is valid and readable

$dir = opendir($path);

while ($file = readdir($dir)) {

print "$file\n";

closedir($dir);

This code opens and iterates through files in a directory. When there are no more files,readdir( )
returns false, the while ends, and the directory is closed. It works perfectly.

It works perfectly, that is, until someone places a file named0 in the directory. Then, $file is set to
0, and the loop terminates early because while (0) evaluates as false. Oops.
The correct way to read files from a directory is as follows:

while (false != = ($file = readdir($dir))) {

print "$file\n";

You need to do a strict equality check using the = = = operator. Even = = gives you the wrong
results because PHP autoconverts $file from a string to a Boolean.

Since there's no reason for you (or anyone else) to remember this, PHP 5 has an improvedforeach
that iterates through certain types of objects in a controlled manner. These objects are called
iterators.

Iterators help eliminate problems in your code. For instance, the Standard PHP Library (SPL)
extension provides a DirectoryIterator:

foreach (new DirectoryIterator($path) as $file) {

print "$file\n";

This example produces the same results as the earlier code that uses the directory functions, but this
code is shorter and safer because you cannot forget the = = = comparison.

SPL contains both standalone iterators and iterators meant to be chained together in a sequence. For
example, you can pipe the output of a DirectoryIterator to an iterator that discards files that don't
end in .html. This design helps you create a series of modular iterators that you can quickly combine
to solve tasks.

SPL is bundled with PHP 5 and enabled by default, so it's essentially part of PHP 5 itself.Table 6-3 at
the end of this chapter has a complete overview of all the different classes withIterator in their
name, including some not covered in this chapter.

Despite all these useful iterators, you're bound to want additional ones. Fortunately, iterators don't
have to be coded in C. You can easily code iterators in PHP, for looping through LDAP or IMAP
listings, or even cycling through results fetched from Amazon.com using REST.

You can also embed an iterator inside a class using aggregation. When the object is iterated inside a
foreach loop, the embedded iterator is used, effectively turning your objects into iterators.

For example, you can iterate over SQLite query results without explicitly fetching each row:

$db = new SQLiteDatabase($database);

foreach($db->query($sql) as $row) {

// operate on $row
}

The SQLiteResult class has a built-in iterator that calls the object's fetch( ) method during
iteration, and you can do the same for your own PHP classes. Normally, when you iterate over an
object with foreach, PHP cycles through the object's properties. In PHP 5, you can override this
behavior and make PHP loop over whatever you decide.

This chapter starts with iteration basics, by first showing how to iterate through a directory using PHP
4 and then comparing that to PHP 5's DirectoryIterator iterator. Afterward, there's a version of
DirectoryIterator written in PHP.

The next iterator discussed in this chapter simplifies an example from earlier in the book: handling
the results of MySQL multi-queries. Without an iterator, navigating through multi-query results is
tricky, but this challenge is made simple using MySQLiQueryIterator.

Iterators don't have to work alone. You can combine them to filter and limit results.SPL has meta-
iterators, or iterators that need to be fed another iterator to be useful.

For instance, using the FilterIterator in conjunction with a DirectoryIterator allows you to
eliminate files that don't end in .html from your directory output.

Then there's the LimitIterator, which allows you to "page" through an iterator's results, using the
same syntax as the SQL LIMIT clause. Combining it with the SimpleXMLIterator lets you redeploy
your database logic onto XML documents.

After regular iterators, the chapter moves on to recursive iterators. Normal iterators only work for a
single list of items, but it's quite common to have lists within lists. Directories contain files, but they
can also contain other directories. To recurse through multiple levels, you need to upgrade your
iterator to handle this complexity.

SPL's RecursiveDirectoryIterator class solves this task. Again, this iterator is recreated in PHP, to
demonstrate the additional methods.

Once that's complete, there's a slight shift as this chapter moves back to traditional array and object
iteration. PHP 5 brings a few changes to these old favorites to account for property access
restrictions.

The chapter then shows how to implement the IteratorAggregate method. This lets a class return a
custom iterator that you control, instead of using PHP's default behavior.

Finally, the last section lists all the SPL classes and interfaces, so you can get a bird's-eye view of the
material and see what else SPL has to offer.

< Day Day Up >


< Day Day Up >

6.1 Before and After: Using Iterators


This section shows you how to use and write basic iterators. It begins by comparing directory
iteration with and without an iterator and then shows you how to write aDirectoryIterator in PHP.

All the directory iteration examples process the set of files shown inFigure 6-1.

Figure 6-1. Files located in /www/www.example.com/

6.1.1 PHP 4: Reading Files in a Directory


In PHP 4, you read files by using opendir( ), readdir( ), and closedir( ):

$dir = opendir('/www/www.example.com/');

while (false != = ($file = readdir($dir))) {

print "$file\n";

closedir($dir);

..
email.html

images

includes

index.html

search.html

The opendir( ) function returns a directory handle. Then, you usually pass the handle to readdir(
) inside a while loop. When iteration is complete, you clean up with closedir( ).

As noted in the introduction, this looks simple, but you can run into problems if you're not careful.
The check inside the while cannot be $f = readdir($d), because then a file named 0 would
terminate the loop.

The results from the preceding code include files of all types, including directories. To filter out
directories, call is_file( ) on the results:

$d = '/www/www.example.com/';

$dir = opendir($d);

while (false != = ($file = readdir($dir))) {

if (is_file($d . DIRECTORY_SEPARATOR . $file)) {

print "$file\n";

closedir($dir);

email.html

index.html

search.html

Again, this check requires more subtlety than it should. If you don't use the PHP constant
DIRECTORY_SEPARATOR and instead hardcode a slash, your script will break if it's run on a Windows
machine.

6.1.2 PHP 5: Reading Files in a Directory


A better solution is to use the directory iterator class, DirectoryIterator. This class is part of SPL,
so you don't need to include it before you use it.

DirectoryIterator takes a pathname as its constructor and then lets you loop through the files in
the path:

$dir = new DirectoryIterator('/www/www.example.com/');

foreach ($dir as $file) {

print "$file\n";

..

email.html

images

includes

index.html

search.html

This code produces the same output as the more complex while loop that uses the directory
functions.

Also, eliminating directories doesn't require the use of a special PHP constant:

$dir = new DirectoryIterator('/www/www.example.com/');

foreach ($dir as $file) {

if (! $file->isDir( )) {

print "$file\n";

email.html

index.html

search.html
Using the isDir( ) method neatly sidesteps the platform-specific issue since this detail is handled
only once, inside the iterator itself.

6.1.3 DirectoryIterator Methods


The isDir( ) method is not the only method of the DirectoryIterator class. Other methods return
the file's full path, the current path, and determine whether the file is a "dot." In Unix and Mac OS X,
every directory automatically contains two special files named dot (.) and dot dot (..), which are links
to the current directory and parent directory, respectively. If you're not careful, they can wreak
havoc upon directory processing.

Table 6-1 lists some of the major DirectoryIterator methods.

Table 6-1. Important DirectoryIterator methods

Return
Name Description
type
getPath( ) String Returns the opened path (e.g., /www/www.example.com)
getFilename(
String Returns the current filename (e.g., index.html)
)

getPathname( Returns the current path and file (e.g.,


String
) /www/www.example.com/index.html)
isDir( ) Boolean Determines whether the current file is a directory
isDot( ) Boolean Determines whether the current file is a dot file (i.e., "." or "..")

< Day Day Up >


< Day Day Up >

6.2 Implementing the Iterator Interface


In the previous example, an SPL class was used as an iterator. However, it's also useful to write your
own iterators. This isn't difficult-iterators are just PHP classes that implement the Iterator
interface.

The Iterator interface is a set of five methods: rewind( ), valid( ), key( ), current( ), and
next( ). (The names are similar to the old PHP array iteration functions, but they don't act
identically, so don't confuse them.) These methods tell PHP how to pull the next item from your list,
when there are no additional items, how to reset the list and start over from the beginning, and so
forth.

When iterating over an object using foreach( ), the methods are called like this:

$it = new MyIterator; // MyIterator is an class that implements Iterator

for ($it->rewind( ); $it->valid( ); $it->next( )) {

$key = $it->key( );

$value = $it->current( );

// code inside the foreach starts here:

print "$key: $value\n";

unset($it);

You must implement all five methods. However, depending on how your class operates, some of
them can be empty.

Table 6-2 describes what each method does and what values they need to return. You cannot pass
any information to these methods; instead, you must store data in object properties.

Table 6-2. Iterator interface methods

Method Description Returns


rewind( ) Resets iterator list to its start Void (nothing)
Method Description Returns
Says if there are additional items left in the true if additional items; false
valid( )
list otherwise
next( ) Moves iterator to the next item in the list Void (nothing)
key( ) Returns the key of the current item Mixed
current(
Returns the current item Mixed
)

Example 6-1 demonstrates an implementation of DirectoryIterator in PHP.

Example 6-1. Implementing DirectoryIterator in PHP

// Class is called MyDirectoryIterator to prevent a

// namespace clash with SPL's DirectoryIterator.

class MyDirectoryIterator implements Iterator {

protected $dir;

protected $path;

protected $key;

protected $file;

protected $valid;

public function _ _construct($path) {

$this->dir = opendir($path);

$this->path = $path;

public function _ _destruct( ) {


closedir($this->dir);

protected function readDir( ) {

return ($this->file = readdir($this->dir)) != = false;

public function rewind( ) {

rewinddir($this->dir);

$this->key = 0;

$this->valid = $this->readDir( );

public function valid( ) {

return $this->valid;

public function key( ) {

return $this->key;

public function current( ) {

return $this->file;

public function next( ) {


$this->key++;

$this->valid = $this->readDir( );

The constructor takes a pathname, just as DirectoryIterator does, and stores the directory handle
in a protected property named $dir. It also saves the path, but this property isn't used right
now-it's for an example later in the chapter. (If only all programs could be written with this kind of
uncanny foresight.) The destructor closes the handle using closedir( ) when the iterator is used
up.

The rewind( ) method calls rewinddir( ) on the handle to restore the internal directory pointer to
the start. It also sets the count to 0. Since there's no natural notion of a key for a directory file, this
class mimics the behavior of DirectoryIterator. The first file is given a key of 0, the next gets a
key of 1, and so forth. Last, it reads in a file by callingreadDir( ) and sets $valid to the result.

The readDir( ) method calls the PHP function readdir( ) and saves the result in the $file
property. If this function returns false, then readDir( ) also returns false; otherwise, readDir( )
returns true. Although it's still necessary to strictly check the return value ofreaddir( ), you can
isolate this comparison to a single location within your entire code base. When common code isn't
duplicated again and again, it's easier to repair bugs because you can fix them all at once.

Using the PHP directory functions, there's no way to check if a directory has another file without
trying to retrieve the file. Therefore, valid( ) just returns the value of the valid property that's set
in readDir( ).

The key( ) and current( ) methods return the value stored in the object properties count and
current. Since rewind( ) is called before either of these are accessed, they will always be set.

The next( ) method is quite similar to rewind( ). It also reads in a new file and sets $valid
accordingly, but instead of resetting $key to 0, it increments it by 1.

Use MyDirectoryIterator exactly how you use DirectoryIterator:

$dir = new MyDirectoryIterator('/www/www.example.com/');

foreach ($dir as $file) {

print "$file\n";

..

email.html

images
includes

index.html

search.html

With this implementation, you can't add additional methods, such asisDir( ), to the class, because
the class returns the filename instead of a file object, just likeDirectoryIterator. However, you
can modify MyDirectoryIterator to create a MyFile object that allows you to call methods on
$file.

< Day Day Up >


< Day Day Up >

6.3 MySQL Query Iterator


Iterators are for more than just directories. You can also apply them to database queries. InChapter
3, you saw MySQL's new mysqli_multi_query( ) function, which is used to send multiple queries at
once. However, processing involves a minimum of four functions and ado/while loop. Yikes!

Despite these complexities, the essential action is iteration: in this case, you're iterating through
database results. The iteration requires more steps than a normal query, but in its essence, it's just
iteration.

This is the perfect task to encapsulate inside an Iterator. The MySQLiQueryIterator takes a nasty
string of code:

if (mysqli_multi_query($db, $query)) {

do {

if ($result = mysqli_store_result($db)) {

while ($row = mysqli_fetch_row($result)) {

print "$row[0]\n";

mysqli_free_result($result);

} while (mysqli_next_result($db));

and transforms it into something clear and simple:

foreach (new MySQLiQueryIterator($db, $query) as $result) {

if ($result) {

while ($row = mysqli_fetch_row($result)) {

print "$row[0]\n";

}
}

Example 6-2 demonstrates the code for MySQLiQueryIterator.

Example 6-2. Implementing a MySQL multi-query iterator

class MySQLiQueryIterator implements Iterator {

protected $link;

protected $query;

protected $key;

protected $valid;

protected $result;

public function _ _construct($link, $query) {

$this->link = $link;

$this->query = $query;

public function rewind( ) {

$this->key = 0;

if (mysqli_multi_query($this->link, $this->query)) {

$this->result = mysqli_store_result($this->link);

$this->valid = true;

} else {

$this->result = false;

$this->valid = false;

}
public function valid( ) {

// mysqli_more_results( ) is one ahead of Iterator valid( )

$valid = $this->valid;

$this->valid = mysqli_more_results($this->link);

return $valid;

public function key( ) {

return $this->key;

public function current( ) {

return $this->result;

public function next( ) {

if ($this->result) { mysqli_free_result($this->result); }

$this->key++;

if (mysqli_next_result($this->link)) {

$this->result = mysqli_store_result($this->link);

} else {

$this->result = false;

}
This iterator takes the MySQL multi-query methods and redistributes them throughout the different
interface methods.

The constructor stores the database handle and the SQL query. Unlike MyDirectoryIterator, this
iterator assumes you already have an open connection to the database because it's common to make
many database queries in a single page.

The rewind( ) method sets the key to 0 and executes the query using mysqli_multi_query( ). If
the query succeeds, the method saves the output of mysqli_store_result( ) in the result
property and sets the valid property to true; if it fails, then both properties are set to false.

Next, the valid( ) method returns the result from mysqli_more_results( ), but there's a catch.
The Iterator interface wants to know if the current element is valid, but mysqli_more_results( )
tells you if the next element is valid.

The solution is to return one value "behind" mysqli_more_results( ) by storing the old value in a
temporary variable, fetching the new results, and then returning the old value. This allows you to
fetch new data without causing an off-by-one error.

The key( ) and current( ) methods aren't very exciting, as they only return the values stored in
their properties. These methods should never alter the iterator's state, because it's legal to call them
multiple times inside the block.

Finally, the next( ) method fetches an additional row using mysqli_store_result( ). However,
before it does this, it releases the old result data and increments the key.

Here's an example of the iterator in action:

$db = mysqli_connect('db.example.org');

$query = >>>_SQL_

DROP TABLE IF EXISTS users;

CREATE TABLE users(username VARCHAR(50) UNIQUE,

password VARCHAR(50));

INSERT INTO users VALUES('rasmus', 'z.8cMpdFbNAPw');

INSERT INTO users VALUES('zeev', 'asd34.23NNDeq');

SELECT username FROM users;

_SQL_;

foreach (new MySQLiQueryIterator($db, $query) as $result) {


if ($result) {

while ($row = mysqli_fetch_row($result)) {

print "$row[0]\n";

rasmus

zeev

The SQL creates a new users table and populates it with two rows, one for rasmus and another for
zeev. This SQL and the database connection are then passed to MySQLiQueryIterator.

The iterator executes the query and returns the results bit by bit. When$result is false, it means
that line of the query didn't return any rows. This is common because some queries- CREATE, DROP,
and INSERT, for instance-don't return rows.

You could modify the iterator to automatically consume these results and only return data that's
fetchable. However, the example's design allows you to track every single query. This is useful when,
for example, you're doing a bulk INSERT of data and want to track your progress by printing out
every 10th row. This would be impossible if the iterator didn't return all results.

When $result is true, you can operate on the result handle using any MySQL retrieval function you
want. This example fetches each row and prints out the first column, which contains usernames.

< Day Day Up >


< Day Day Up >

6.4 Chaining Iterators


An important iterator feature is the ability to chain them together so they can act as a series of
filters. For example, you can use iterators to restrict results to words that match a particular regular
expression or to return only the first 10 results.

I call these types of iterators meta-iterators. SPL comes with two meta-iterators: FilterIterator,
to filter results, and LimitIterator, to limit results.

6.4.1 Filtering Results with FilterIterator


FilterIterator is an abstract class that implements all the methods of a regular Iterator.
However, it has a twist-you must define an accept( ) method that controls whether an item should
be returned or filtered out from the results.

Unlike DirectoryIterator, which is directly instantiable, you cannot create a new FilterIterator.
Instead, you must extend it and implement accept( ).

Here's an example that filters by a Perl-compatible regular expression:

class RegexFilter extends FilterIterator {

protected $regex;

public function _ _construct(Iterator $it, $regex) {

parent::_ _construct($it);

$this->regex = $regex;

public function accept( ) {

return preg_match($this->regex, $this->current( ));

}
}

RegexFilter takes two arguments in its constructor: an Iterator to filter and a regular expression
pattern to use as a filter. The first parameter is passed on to the parentFilterIterator
constructor, because it handles the iteration for your class.

The regular expression (regex for short) is stored in a protected property, $regex, for use inside the
accept( ) method. This method must return true for items you wish to return and false for the
ones that should be removed.

Handily, preg_match( ) returns the number of times the pattern matches the string. This number is
always 0 or 1. (For multiple matches, use preg_match_all( ).) Since 1 evaluates as true and 0 as
false, you can directly pass along preg_match( )'s return value.

Because $regex is a Perl-compatible regular expression, you must place pattern delimiters around
your regex. The regular expression is checked against the value of$this->current( ).
Alternatively, you could check it against $this->key( ), depending on how you want the iterator to
work.

This example allows you to use RegexFilter inline with a foreach loop and DirectoryIterator:

$dir = new DirectoryIterator('/www/www.example.com/') {

$filtered_dir = new RegexFilter($dir, '/html$/i');

foreach ($filtered_dir as $file);

print "$file\n";

email.html

index.html

search.html

You pass RegexFilter two arguments, an iterator and the regular expression pattern. This example
returns files ending with html and eliminates the others. The regular expression uses the /i modifier
to do a case-insensitive check.

When you only want to iterate over the objects once, you can create both RegexFilter and
DirectoryIterator inside the foreach:

foreach (new RegexFilter(

new DirectoryIterator('/www/www.example.com/'), '/html$/i')


as $file) {

print "$file\n";

email.html

index.html

search.html

6.4.2 Limiting Results with LimitIterator


Another useful meta-iterator is LimitIterator. This Iterator, which behaves just like the SQL
LIMIT clause, allows you to filter nondatabase listings with the same logic you use with a database.

This example returns only the third and fourth files in the directory:

foreach(new LimitIterator(

new DirectoryIterator('/www/www.example.com/'), 2, 2)

as $file) {

print "$file\n";

email.html

images

Unlike FilterIterator, LimitIterator doesn't require you to implement a method; therefore, it's
directly instantiable. You don't need to extend it.

Its constructor takes three arguments: the iterator, the start position, and the number of items to
return. Items start at position 0, so the third is at position 2. That's why this example passes 2, 2 as
the second and third parameters.

Of course, you can chain three Iterators in combination. For example, to find only the first file
ending in html in the /www/www.example.com/ directory:

foreach(new LimitIterator(new RegexFilter(

new DirectoryIterator('/www/www.example.com/'), '/html$/i'), 0, 1)

as $file) {

print "$file\n";
}

email.html

It's important to order the Iterators correctly, or you won't get the results you expect. Don't switch
RegexFilter and LimitIterator:

foreach(new RegexFilter(new LimitIterator(

new DirectoryIterator('/www.www.example.com/'), 0, 1), '/html$/i')

as $file) {

print "$file\n";

This prints nothing! LimitIterator returns only one record-the file named ".". Then,
FilterIterator comes along, sees that the record doesn't end in html, and eliminates it. This
leaves you with no results.

< Day Day Up >


< Day Day Up >

6.5 SimpleXML Iterator


The SimpleXML iterator allows you to use SimpleXML objects with the other iterator classes. This
allows you to easily construct an SQL-like interface to XML files.

For instance, suppose you have an XML address book like the one inExample 5-1. However, instead
of containing only 2 records, it contains hundreds of records, and you want to display the records in
groups of 10.

One solution is to place the information into a database. Another is to use XPath. A third is to
combine a SimpleXMLIterator with a LimitIterator:

$ab = file_get_contents('address-book.xml');

$page = 0;

$limit = 10;

foreach (new LimitIterator(new SimpleXMLIterator($ab),

$page * $limit, $limit) as $person) {

print "$person->firstname $person->lastname: $person->email\n";

The SimpleXMLIterator takes a string of XML and creates an iterable SimpleXML object. Here, it's
parsing address-book.xml.

This iterator is then passed to a LimitIterator that restricts the output to only the $limit number
of records beginning at $page * $limit. When $page is 0, it returns the first 10 records; when
$page is 1, you get records 11 through 20.

The $person item can be treated like a SimpleXMLElement, so $person->firstname returns the text
stored in the firstname element.

< Day Day Up >


< Day Day Up >

6.6 Before and After: Recursive Directory Iteration


The previous iterator examples handle only a flat list of items, but frequently your lists contain other
lists. For instance, a directory can have other directories inside it, those child directories can contain
additional directories, and so on.

Solve this problem with a recursive iterator, an iterator that works with multilevel lists. The following
examples demonstrate directory iteration for subdirectories.

6.6.1 PHP 4: Recursively Reading Files in a Directory


In PHP 4, the easiest way to process all the files in a directory and its children is to call a function
recursively:

function iterate_dir($path) {

$files = array( );

if (is_dir($path) & is_readable($path)) {

$dir = dir($path);

while (false != = ($file = $dir->read( ))) {

// skip . and ..

if (('.' = = $file) || ('..' = = $file)) {

continue;

if (is_dir("$path/$file")) {

$files = array_merge($files, iterate_dir("$path/$file"));

} else {

array_push($files, $file);

$dir->close( );
}

return $files;

$files = iterate_dir('/www/www.example.com');

foreach ($files as $file) {

print "$file\n";

email.html

logo.gif

php.gif

auth.inc

user.inc

index.html

search.html

This function loops through every file in the current directory. If the file is a directory, the function
recursively calls itself and passes the subdirectory name as the argument. These results are then
merged back into a master list of files stored in the $files array. When a file is not a directory, it's
added to the list using array_merge( ).

Repeatedly calling iterate_dir( ) is slow, but it allows you to order the files so that children live
under their parents.

Prepending $path to the filename before it's merged into the $files array modifies the example
output to include the full path:

function iterate_dir($path) {

// same as before

if (is_dir("$path/$file")) {
$files = array_merge($files, iterate_dir("$path/$file"));

} else {

array_push($files, "$path/$file");

// same as before

$files = iterate_dir('/www/www.example.com');

foreach ($files as $file) {

print "$file\n";

/www/www.example.com/email.html

/www/www.example.com/images/logo.gif

/www/www.example.com/images/php.gif

/www/www.example.com/includes/auth.inc

/www/www.example.com/includes/user.inc

/www/www.example.com/index.html

/www/www.example.com/search.html

6.6.2 PHP 5: Recursively Reading Files in a Directory


PHP 5 replaces that complicated work with a RecursiveDirectoryIterator. Use it like this:

$dir = new RecursiveIteratorIterator(

new RecursiveDirectoryIterator('/www/www.example.com/'));

foreach ($dir as $file) {

print "$file\n";
}

email.html

logo.gif

php.gif

auth.inc

user.inc

index.html

search.html

No, that's not a typo-there really is a class named RecursiveIteratorIterator. This class is an
Iterator for classes that implement the RecursiveIterator interface. Think of PHP as
automatically implementing an IteratorIterator as part of foreach, but to ensure children are
properly traversed, you need to use this SPL class.

Also, if you look closely, this output is different than that returned by the PHP 4 example. Something
has filtered out all the directories from the listing. To fix this, passtrue as a second argument to the
RecursiveIteratorIterator:

$dir = new RecursiveIteratorIterator(

new RecursiveDirectoryIterator('/www/www.example.com/'), true);

foreach ($dir as $file) {

print "$file\n";

email.html

images

logo.gif

php.gif

includes

auth.inc

user.inc

index.html
search.html

That's still not the most readable output, because you don't know which files live in which folders.
Prepending the path helps:

$dir = new RecursiveIteratorIterator(

new RecursiveDirectoryIterator('/www/www.example.com'), true);

foreach ($dir as $file) {

print $file->getPathname( ) . "\n";

/www/www.example.com/email.html

/www/www.example.com/images

/www/www.example.com/images/logo.gif

/www/www.example.com/images/php.gif

/www/www.example.com/includes

/www/www.example.com/includes/auth.inc

/www/www.example.com/includes/user.inc

/www/www.example.com/index.html

/www/www.example.com/search.html

A quick call to the getPathname( ) method solves the problem. You may notice that you can call
both print $file and $file->getPathname( ). Is $file a string or an object? It's an object, but it
has a _ _toString( ) method that returns the file's name when it's printed.

Alternatively, it would be nice to display these files using a pretty directory tree-style listing. For that,
you need to implement a custom class that extends RecursiveIteratorIterator:

class DirectoryTreeIterator extends RecursiveIteratorIterator {

function current( ) {

return str_repeat('| ', $this->getDepth( )) . '|-' .

parent::current( );
}

$dir = new DirectoryTreeIterator(

new RecursiveDirectoryIterator('/www/www.example.com/'), true);

foreach ($dir as $file) {

print $file. "\n";

|-email.html

|-images

| |-logo.gif

| |-php.gif

|-includes

| |-auth.inc

| |-user.inc

|-index.html

|-search.html

The DirectoryTreeIterator class, instead of returning a simple object that represents the file,
provides a graphical representation of the directory hierarchy.

Implementing this class requires a new method that's part of RecursiveIteratorIterator:


getDepth( ). The getDepth( ) method returns a number that indicates how many levels down the
current item lives. For files in the top-level directory, it returns 0; for files inside theimages and
includes directories, it returns 1; and so on.

With getDepth( ), it's easy to prepend a pipe and space (| ) for each level beyond the first. You
could use a for loop here from 0 to getDepth( ), but it's faster to use str_repeat( ). After those
characters come a pipe and dash (|-) and then the filename. This produces a simple graphic
directory tree.
< Day Day Up >
< Day Day Up >

6.7 Implementing the RecursiveIterator Interface


The RecursiveIterator interface extends the existing Iterator interface to include two additional
methods: hasChildren( ) and getChildren( ). These methods are used to navigate through
sublists.

If you have an existing Iterator, it's natural to extend it to include the additional methods. SPL
actually comes with a RecursiveDirectoryIterator, so there's no need to add these methods to
DirectoryIterator, but it's a useful exercise to see how these methods should behave.

Here's how it's done:

class MyRecursiveDirectoryIterator extends MyDirectoryIterator

implements RecursiveIterator {

protected function getPath( ) {

return $this->path . DIRECTORY_SEPARATOR . $this->current( );

public function hasChildren( ) {

return is_dir($this->getPath( ));

public function getChildren( ) {

return new MyRecursiveDirectoryIterator($this->getPath( ));

Wow! This is much shorter than the code that was needed for iterate_dir( ). Since you can inherit
methods from your earlier iterator, the implementation is three one-line methods.

The first method, getPath( ), constructs a complete pathname from the original path and the
current filename. It uses the $path property that was stored in the constructor for
MyDirectoryIterator in Example 6-1 because I knew you'd need it now.

The other two methods required by the interface also turn out to be easy to code. A file has children
if it's a directory, so hasChildren( ) only needs to check the return value of is_dir( ).

Likewise, you can create a child iterator for a directory by instantiating a new instance of
MyRecursiveDirectoryIterator and using the subdirectory's path in the constructor. This is
analogous to calling iterate_dir( ) inside of iterate_dir( ).

Notice how there's no need to keep track of where you've been, what files you've seen, and when a
directory needs to be processed. This is all taken care of by theRecursiveIteratorIterator and is
another major advantage of iterators.

If you actually run this code, however, it doesn't work:

$dir = new DirectoryTreeIterator(

new MyRecursiveDirectoryIterator('/www/www.example.com/'), true);

foreach ($dir as $file) {

print "$file\n";

You end up with an amusing series of warnings, including this:

PHP Warning: opendir(././././././././././././././././././././././././

./././././././././././././././././././././././././././././././././././

./././././././././././././././././././././././././././././././././././

./././././././././././././././././././././././././././././././././././

./././././././././././././././././././././././././././././././././././

./././././././././././././././././././././././././././././././././././

./././././././././././././././././././././././././././././././././././

./././././././././././././././.): failed to open dir: Too many open

files

PHP just spirals off into an endless cycle. The directory files . and .. cause an infinite loop as the same
directory is opened repeatedly, again and again.

It turns out the best solution is eliminating those two files completely. This requires using one new
method and redefining two more:
public function isDot( ) {

return ($this->current( ) = = '.' ||

$this->current( ) = = '..');

public function rewind( ) {

rewinddir($this->dir);

$this->key = 0;

while ($this->valid = $this->readDir( ) and

$this->isDot( )) { }

public function next( ) {

$this->key++;

while ($this->valid = $this->readDir( ) and

$this->isDot( )) { }

The isDot( ) method returns true if a file is one of the two nasty directory links. The method is
used in rewind( ) and next( ) to detect those files and cause the iterator to skip over them. These
redefined methods no longer retrieve just a single file usingreadDir( ), but use a while loop to
keep retrieving files until they get one that isn't a dot.

Everything now works as expected:

$dir = new DirectoryTreeIterator(

new MyRecursiveDirectoryIterator('/www/www.example.com/'), true);

foreach ($dir as $file) {

print "$file\n";
}

|-email.html

|-images

| |-logo.gif

| |-php.gif

|-includes

| |-auth.inc

| |-user.inc

|-index.html

|-search.html

Most recursive iterators don't contain these special self-referential aliases, so they don't need to go
through the complicated steps necessary in this example. Directories are a unique case.

< Day Day Up >


< Day Day Up >

6.8 Array and Object Property Iteration


In PHP you can iterate through all the elements in an array using foreach. Although other methods
are available, this is the preferred syntax for looping though results:

$person = array('firstname' => 'Rasmus',

'lastname' => 'Lerdorf');

foreach ($person as $key => $value) {

print "$key: $value\n";

firstname: Rasmus

lastname: Lerdorf

You can also use foreach to view an object's properties:

class Person {

// hardcode values for demonstration

public $firstname = 'Rasmus';

public $lastname = 'Lerdorf';

$person = new Person;

foreach ($person as $property => $value) {

print "$property: $value\n";

firstname: Rasmus
lastname: Lerdorf

In PHP 5, if an object property cannot be accessed because it is set asprotected or private, then it
will be skipped during iteration.

For example, update Person to include a private email attribute:

class Person {

// hardcode values for demonstration

public $firstname = 'Rasmus';

public $lastname = 'Lerdorf';

private $email = '[email protected]';

$person = new Person;

foreach ($person as $property => $value) {

print "$property: $value\n";

firstname: Rasmus

lastname: Lerdorf

The email property is not printed, because you cannot access it from outside the class, but the
results are different when you do a foreach inside the class. For instance, add a method named
printInfo( ):

class Person {

// hardcode values for demonstration

public $firstname = 'Rasmus';

public $lastname = 'Lerdorf';

private $email = '[email protected]';

public function printInfo( ) {


foreach ($this as $property => $value) {

print "$property: $value\n";

$person = new Person;

$person->printInfo( );

firstname: Rasmus

lastname: Lerdorf

email: [email protected]

While the code inside printInfo( ) is the same as the foreach outside the class (except that you
refer to the object as $this instead of $person), the first loop cannot see the private email
property, because it is restricted from public access. Class methods, however, are able to view all
three properties, so email is printed.

< Day Day Up >


< Day Day Up >

6.9 Redefining Class Iteration


By default, when you iterate over an object, PHP serves up all of the object's properties. That's great
for debugging, but it isn't what you want in every situation.

For example, you've mapped all property accesses in your class though the special_ _get( ) and _
_set( ) methods. In this implementation, you're storing all the data in a protected array named
$data. It's logical that when someone iterates over the class, they're served up another element of
$data. However, as things stand now, not only would they see other variables, but they'renot going
to see $data, because it's protected.

Fortunately, PHP 5 gives you the ability to control which items should appear during iteration. This
lets you refine the default behavior to what's appropriate for the class.

Before getting into the specifics, here's a Person class for the later examples:

class Person {

protected $data;

public function _ _construct($firstname, $lastname) {

$this->firstname = $firstname;

$this->lastname = $lastname;

public function _ _get($p) {

if (isset($this->data[$p])) {

return $this->data[$p];

} else {

return false;

}
public function _ _set($p, $v) {

$this->data[$p] = $v;

public function _ _toString( ) {

return "$this->firstname $this->lastname";

The Person class has a constructor that stores the person's firstname and lastname. Since property
access has been overridden by _ _get( ) and _ _set( ), $this->firstname really references
$this->data['firstname'].

There's also a _ _toString( ) method that defines how to print the person. That means you can
use a Person object in a print statement and it'll look nice:

$person = new Person('Rasmus', 'Lerdorf');

print $person;

Rasmus Lerdorf

However, when you iterate over $person, nothing appears:

$person = new Person('Rasmus', 'Lerdorf');

foreach($person as $property => $value) {

print "$property: $value\n";

Since $data is protected, the foreach omits it. You could change $data's visibility to public, but
that defeats the whole purpose of using accessors.

Not surprisingly, this is the perfect opportunity for a custom iterator. This requires two steps. First,
you need to implement the IteratorAggregate interface. This interface tells PHP that you're going
to provide your own iterator for the class and also lets PHP know where to get it. Second, you must
return an object that implements the Iterator interface containing the data for PHP to iterate over.
In essence, you're aggregating an Iterator object into your original class. If you remember from
way back in Chapter 2, aggregation is when you embed one class inside another and arrange it so
that when a person calls methods on the main class, they're actually handled by the embedded class.

6.9.1 Implementing IteratorAggregate


IteratorAggregate is a simple interface because you must add only a single method to your class:
getIterator( ).

When you iterate over a class that implements IteratorAggregate, instead of looping through the
object's properties, PHP calls getIterator( ) and iterates through whatever is returned by the
method.

Here's how to modify Person:

class Person implements IteratorAggregate {

// Everything from before, plus:

public function getIterator( ) {

// insert code to return Iterator here...

Person now implements IteratorAggregate and has a getIterator( ) method, but there's still
the question of what the method should return.

6.9.2 Turning Arrays and Objects into Iterators


At first glance, the solution seems trivial. Since $data is an array and PHP can iterate over arrays,
$data is an iterator. Just return $data and you're done.

Unfortunately, that doesn't work. PHP requires an actual object that implements Iterator. It won't
accept an array as a substitute even though it acts similarly. Therefore, you need to find a way to
convert an array into an Iterator that behaves like an array.

You could solve this by writing your own Iterator, but the SPL extension provides an ArrayObject
class that does exactly this for both arrays and objects:

$data = array('firstname' => 'Rasmus',


'lastname' => 'Lerdorf');

foreach (new ArrayObject($data) as $key => $value) {

print "$key: $value\n";

firstname: Rasmus

lastname: Lerdorf

From an external point of view, there's little difference between usingArrayObject and iterating over
the array directly. When you pass ArrayObject an array, it allows you to loop over its elements.
When you pass ArrayObject an object, it allows you to loop over the object's properties. Since that's
how arrays and objects already work, the difference is subtle.

However, the ArrayObject class has the advantage of turning your variable into an Iterator. Since
PHP requires that getIterator( ) return an Iterator instead of an array, this is an extraordinarily
handy class to solve the problem of what to do with $data.

Armed with ArrayObject, the implementation is easy:

public function getIterator( ) {

return new ArrayObject($this->data);

Now you can iterate over the object:

$person = new Person('Rasmus', 'Lerdorf');

foreach ($person as $property => $value) {

print "$property: $value\n";

firstname: Rasmus

lastname: Lerdorf

That completes the work necessary to make Person iterable, but ArrayObject has one additional
advantage worth mentioning. Although using foreach on a variable is easy, it's not particularly
memory efficient, because PHP does not directly operate on the variable. Instead, it first makes a
copy of the variable and loops through the duplicate.

ArrayObject optimizes array iteration because it makes fewer internal copies of the array. This
speeds up code. Therefore, if speed is important in your application, make this switch:

$person = array('firstname' => 'Rasmus',

'lastname' => 'Lerdorf');

// FAST

foreach ($person as $key => $value) {

print "$key: $value\n";

// FASTER

foreach (new ArrayObject($person) as $key => $value) {

print "$key: $value\n";

6.9.3 Writing Dynamic Iterable Objects


The iterator returned by getIterator( ) doesn't need to return data already stored in the object.
You can return any iterator, even one that accesses external information.

You've already briefly seen the SQLite iterator, which returns information from a database query:

$db = new SQLiteDatabase($database);

foreach ($db->query($sql) as $row) {

// operate on $row

Since the SQLite result handle is also an object that implementsIterator, you can use it in your
classes in place of ArrayIterator.

This example creates an object, addressBook. Pass addressBook a Person and it returns all the
information in the database about that person:
class addressBook implements IteratorAggregate {

protected $person;

protected $db;

public function _ _construct(Person $person) {

$this->person = $person;

$this->db = new SQLiteDatabase('address-book.db');

public function getIterator( ) {

$firstname = sqlite_escape_string($this->person->firstname);

$lastname = sqlite_escape_string($this->person->lastname);

return $this->db->query("SELECT * FROM persons

WHERE firstname LIKE '$firstname'

AND lastname LIKE '$lastname'");

$person = new Person('Rasmus', 'Lerdorf');

$ab = new addressBook($person);

foreach($ab as $p) {

// $p contains an array of information about $person

When you create a new addressBook, it saves the $person in a protected property. Then, when you
iterate over the object in a foreach, PHP calls getIterator( ).

Inside getIterator( ), you escape the SQL and make the query. Since the result object implements
the Iterator interface, return it and you're done.

The $p variables are arrays with numerical keys. You can then operate on these fields as if you had
called SQLite's sqlite_fetch_array( ) function.

For more on SQLite, see Chapter 5.

< Day Day Up >


< Day Day Up >

6.10 Iterator and SPL Classes and Interfaces


This chapter demonstrates many classes, abstract classes, and interfaces, yet it only begins to touch
on the full nature of SPL.

Table 6-3 is a quick-reference guide to the complete collection of SPL iterators, including some not
mentioned in this chapter. For more information, see the SPL section of thePHP Manual at
http://www.php.net/spl.

Table 6-3. Iterator and SPL classes and interfaces

Name Type Relationship Description

Implements
Converts arrays and
ArrayObject Class IteratorAggregate
objects into iterators
interface
Used by ArrayObject;
Implements Iterator
ArrayIterator Class cannot be instantiated
interface
directly

Provides "look ahead by


Implements Iterator
CachingIterator Class one" capability using
interface
hasNext( )

Extends CachingIterator
class; implements Adds caching support to
CachingRecursiveIterator Class
RecursiveIterator recursive iterators
interface
Implements Iterator An iterator for directory
DirectoryIterator Class
interface files
Allows filtering of other
Abstract Implements Iterator
FilterIterator iterators using accept( )
class interface
method
Defines Iterator
Iterator Interface Implements an iterator
interface

Allows classes to control


Defines
iteration by returning
IteratorAggregate Interface IteratorAggregate
iterator from
interface
getIterator( ) method
Name Type Relationship Description
Implements Iterator Emulates database LIMIT
LimitIterator Class
interface clause
Extends FilterIterator
Filters out leaf items, so
class; implements
ParentIterator Class only parents (items with
RecursiveIterator
children) are processed
interface

Implements A recursive iterator for


RecursiveDirectoryIterator Class RecursiveIterator directory files; goes
interface through all subdirectories
Extends Iterator Implements a recursive
RecursiveIterator Interface
interface iterator

An iterator for classes that


Implements Iterator implement
RecursiveIteratorIterator Class
interface RecursiveIterator
interface
Adds ability to seek( )
Extends Iterator
SeekableIterator Interface within an iterator; used by
interface
LimitIterator class

An iterator for XML files


Implements Iterator using SimpleXML functions;
SimpleXMLIterator Class
interface only iterates over XML text
nodes

< Day Day Up >


< Day Day Up >

Chapter 7. Error Handling and Debugging


Bolstering PHP's reputation of practicality, PHP 5 has two new tools for handling errors and debugging
applications: exceptions and backtraces. These topics aren't sexy, but most code has more lines of
error checking than actual application logic, and you likely spend more time tracking down obscure
bugs than writing the original program (although you probably don't like to think about this).

Error handling is particularly important in a networked environment, where you're granting anyone
with an Internet account access to your computer and retrieving data from sites you don't control.
It's vital that you handle problems gracefully for users while not providing information to people who
are looking to break into your system.

Exception handling allows you to trap major errors in a separate section of your code devoted to
processing errors. This allows you to recover gracefully whenever a serious problem occurs.
Exceptions are new to PHP, but are a long-time feature in other languages. Right now, only a few
PHP extensions use exceptions, but they're slowly being phased in. However, they're available right
now for any PHP code you write.

PHP 5 also eases your bug hunt with its debug_backtrace( ) function. Developers with programs
that include files and have complex hierarchies can now easily determine the exact state of their
script without placing lots of print statements in their code.

< Day Day Up >


< Day Day Up >

7.1 Before and After: Handling Errors


This section compares PHP 4's style of error handling, where you check the return values of
functions, with the new PHP 5 method known as exceptions.

7.1.1 PHP 4: Checking Return Values


Traditional error handling as implemented in PHP 4 requires you to interleave a significant amount of
bug-handling code with your program logic. This not only makes scripts hard to understand, but it's
annoying to check every return value.

For instance, Example 7-1 shows how most people use the DOM XML extension.

Example 7-1. Failing to check for errors

$version = '1.0';

$element = 'address-book';

$dom = domxml_new_doc($version);

$ab = $dom->create_element($element);

$ab = $dom->append_child($ab);

It seems unlikely that any of these methods will fail, so most people omit error checking. However,
it's entirely possible that something could go wrong. For instance,create_element( ) fails when the
element name contains illegal characters, such as spaces or ampersands (&). Since this code does
zero error handling, there's no way to identify a problem and proceed accordingly.

Example 7-2 is a rewritten version of Example 7-1 that handles errors.

Example 7-2. Checking return values

$version = '1.0';

$element = 'address-book';
if ($dom = domxml_new_doc($version)) {

if ($ab = $dom->create_element($element)) {

if ($ab = $dom->append_child($ab)) {

// Successfully appended address-book element

} else {

// Cannot append child

} else {

// Cannot create <address-book> element

} else {

// Cannot create new document

When a PHP 4 DOM method has an error, it returns false. This lets you identify problems and handle
them inside the else blocks.

This code is more robust, but it's 16 lines instead of 6. It's also more difficult to decipher. As a result,
many of the benefits of proper error handling are outweighed by these deficits.

7.1.2 PHP 5: Catching Exceptions


PHP 5 provides a different way to identify and track errors. This method is called exception handling.
Exceptions make it possible to handle errors in a clear and comprehensive fashion by avoiding nested
if statements.

An exception is a signal that some error (or other non-normal activity) has occurred. When an
exception occurs, it is "thrown" by the offending method. A thrown exception can be "caught" by a
block of code known as an exception handler, which processes the exception and takes action as
necessary.

Exceptions become more clear when you see them in PHP instead of English. Using PHP 5 and
exceptions, Example 7-2 turns into Example 7-3.

Example 7-3. Catching exceptions


$version = '1.0';

$element = 'address-book';

try {

$dom = new DOMDocument($version);

$ab = new DOMElement($element);

$ab = $dom->appendChild($ab);

} catch (DOMException $e) {

print $e;

Exception handlers operate on code wrapped around a try block. (Here it's checking DOM objects.)
Then, just like you set up an if/else, there's a catch block below the try.

The catch block specifies the type of exception to catch and the variable in which to store it. This
example uses the special DOM exception class calledDOMException, and names it $e. Inside the
catch, you can access $e to find out more information about the problem. $e isn't anything magical;
it's just a variable that PHP populates with an object when an exception is thrown.

The easiest way to get an overview of the exception is by printing$e. This triggers the object's _
_toString( ) method, which returns a formatted version of all the data stored in the object.

For instance, if $element is set to an illegal XML element name, such as &, Example 7-3 prints:

exception 'DOMException' with message 'Invalid Character Error'

in /www/www.example.com/dom.php:7

Stack trace:

#0 {main}

The exception is a DOMException, which tells you it's from the DOM extension. Its message is
Invalid Character Error, and it occurred in on line 7 of /www/www.example.com/dom.php. At the
bottom is a stack trace, something that's described in more detail inSection 7.8, later in this chapter.

PHP dies with a fatal error when you fail to catch an exception, as inExample 7-4.

Example 7-4. Failing to catch an exception


$version = '1.0';

$element = '&'; // & is an ILLEGAL element name

$dom = new DOMDocument($version);

$ab = new DOMElement($element);

$ab = $dom->appendChild($ab);

PHP Fatal error: Uncaught exception 'DOMException' with message

'Invalid Character Error' in /www/www.example.com/dom.php:7

Stack trace:

#0 {main}

thrown in /www/www.example.com/dom.php on line 7

You can control this output using a custom exception handler. See Section 7.6, later in this chapter,
for more details.

< Day Day Up >


< Day Day Up >

7.2 The Benefits of Exceptions


Nothing demonstrates the benefits of exceptions more than the previous section. Any time you can
improve upon a piece of code and reduce the overall number of lines, it's a great change.

Another major boon from exceptions is that they "bubble up." When an exception isn't caught in the
method it is thrown, the exception moves up a level in the call stack. In other words, the method that
called the method gets a chance to handle the exception. This process continues until either the
exception is finally caught or you're back in the top level. For example:

function createAddressBook( ) {

$version = '1.0';

$element = 'address-book';

$dom = new DOMDocument($version);

$ab = new DOMElement($element);

$ab = $dom->appendChild($ab);

return $dom;

try {

$ab = createAddressBook( );

} catch (DOMException $e) {

print $e;

Since createAddressBook( ) doesn't catch the exceptions it generates, the exceptions flow back up
a level and can be caught in the try/catch block that wraps around the call to createAddressBook(
).
This behavior makes exceptions particularly well-suited for large-scale applications and frameworks.
Without exceptions, you're forced to check the return value of every method, even at the relatively
high level of development that occurs when you're combining full-blown objects.

However, at that point in your code, checking return values is not always useful, because there's very
little you can do about a problem you discover. By the time it's reached that spot in the code, it's
hard to tell whether the error was triggered by some truly nasty bug deep inside the bowels of your
program, or whether it's a relatively benign warning that occurred one level down. As a result, you're
likely to just log the error, display an innocuous message to the user, and quit.

With exceptions, this form of error handling is trivial. Additionally, it allows you to relocate the code
to handle errors someplace out of the way, so your top-level application logic is easy to read and
unencumbered by error handling.

< Day Day Up >


< Day Day Up >

7.3 System Exceptions


In contrast to some languages, notably Python and Java, PHP 5 doesn't throw exceptions whenever there's
a problem. In fact, it never throws exceptions when you're using a proceduralfunction. Instead, exceptions
are strictly reserved for objects.

Furthermore, even when you're using an object, PHP throws exceptions only in two instances:

Errors in constructors

"Serious" problems in some extensions

These are the two instances when you must place your code inside a try/catch or risk a fatal error.

7.3.1 Constructors
Whenever there's an error during object instantiation, PHP throws an exception to signal failure. It must
throw an exception because a constructor always returns an object, so it cannot returnfalse or another
value to indicate that it couldn't properly create an object.

You've already seen one example of a constructor throwing an exception back in Example 7-3. When you
pass DOMElement an illegal tag name, DOMElement throws an exception.

A non-DOM example is the SQLite extension, which issues an exception when you don't provide a database
name:

try {

$db = new SQLiteDatabase( );

} catch (Exception $e) {

print $e;

exception 'SQLiteException' with message 'SQLiteDatabase::_ _construct( ) expects at least

1 parameter, 0 given' in /www/www.example.com/sqlite.php:2

Stack trace:

#0 {main}
7.3.2 Serious Problems
In PHP 5, extensions throw exceptions only when there's serious logic error. Other errors are still returned
in the traditional manner, by returning a designated error value such asfalse or -1 .

There's no cut-and-dry definition of "serious." It varies from extension to extension, depending on the
circumstances. Some extensions, such as DOM, throw exceptions in accordance with the DOM specification,
which defines those instances as follows:

DOM operations only raise exceptions in "exceptional" circumstances, i.e., when an operation is
impossible to perform (either for logical reasons, because data is lost, or because the implementation
has become unstable). In general, DOM methods return specific error values in ordinary processing
situations.

In contrast, other extensions, such as MySQLi and SQLite, never throw exceptions (except in their
constructors).

Because the decision of if and when to throw exceptions is left up to the extension developer, there's no rule
of thumb to determine whether a given method will or will not issue an exception. The only way to know
how an extension behaves is to read its documentation in the PHP Manual (http://www.php.net/manual/).

< Day Day Up >


< Day Day Up >

7.4 The Exception Class


What you're actually catching when PHP throws an exception is either an instance of theException
class or a class that's descended from Exception.

The methods of the Exception class provide you with easy access to the individual details of the
exception, such as the message, error code, filename, and line number. Use them when the overview
you get from printing an object doesn't help you find the information you need.

For example, you might want to take different action based upon the specific error code stored in the
exception. Some problems may be recoverable, while others aren't.

Table 7-1 contains a list of all the exception methods and what they do.

Table 7-1. Exception methods

Method name Description


getMessage( ) A text description of the error
getCode( ) A numeric error code
getFile( ) The filename where the error occurred
getLine( ) The line where the error occurred
getTrace( ) The array output of debug_backtrace( )

getTraceAsString( ) The string output of debug_print_backtrace( )


_ _toString( ) A comprehensive description of the exception

Example 7-5 calls four of the Exception class methods.

Example 7-5. Retrieving exception specifics

$version = '1.0';

$element = '&'; // & is an ILLEGAL element name

try {
$dom = new DOMDocument($version);

$ab = new DOMElement($element);

$ab = $dom->appendChild($ab);

} catch (Exception $e) {

error_log('Error in file ' . $e->getFile( ) . ' on line ' .

$e->getLine( ) . ".\n" . 'Error message: "' . $e->getMessage( ) .

'" and error code: ' . $e->getCode( ) . '.');

Error in file /www/www.example.com/dom.php on line 7.

Error message: "Invalid Character Error" and error code: 5.

The getFile( ) and getLine( ) methods return the filename and line number where the error
occurred. They're what you would get if you captured the _ _FILE_ _ and _ _LINE_ _ constants at
the location of the error. (Printing these constants inside the catch give you the current filename and
line number inside the catch, not the name and number where the error actually took place.)

The getMessage( ) and getCode( ) methods provide a text description of the error and an error
number. According to the DOM specification, trying to create an element with an illegal name must
generate an invalid character error, which has an error code of 5.

The Exception class also provides access to a backtrace of the current working environment at the
time of the error. Access it through getTraceAsString( ). Since the code in Example 7-5 lives in the
main scope, it's not too exciting or useful:

#0 {main}

There is also getTrace( ), which returns the same information as getTraceAsString( ) but with
each line as an element in an array. Backtraces and their format are the topic of the last section of
this chapter.

As you've seen, DOM actually throws a DOMException instead of a regular Exception. A


DOMException is a subclass of the base Exception class. It doesn't contain any additional methods
or properties, but it lets you easily differentiate DOM errors from errors thrown by other extensions.

Unlike Java, PHP does not support the finally keyword. The finally block designates a region of
code that's always run, regardless of whether there's an exception. It's used to close open
connections and release locked files. The solution is to place as much cleanup code inside your
objects' destructors and handle the remaining details (if any) inside thecatch block.
< Day Day Up >
< Day Day Up >

7.5 User Exceptions


Exceptions aren't restricted to extensions. You can trigger your own exceptions bythrowing an
instance of the Exception class.

User-thrown exceptions are useful because they allow you to integrate the error handling for your
own errors with exceptions thrown by PHP. Inside your methods, you may still need to check a return
value; however, converting those errors into exceptions allows the developer using your classes to
know he can reliably trap all errors using a try/catch block.

This section covers when it's appropriate to throw exceptions of your own, and also how to create
and throw an exception. Chapter 10 contains an address book application that demonstrates, among
other things, how to put these exception-handling techniques into practice.

7.5.1 When to Use Exceptions


There many categories of errors, and exceptions are not always an appropriate way to handle them
in PHP. In general, there are three major groups:

Programming errors

You, the programmer, have made a mistake. For instance, you passed an incorrect number of
arguments to a method.

Client code errors

Your code is okay, but there's a problem with an external resource. For instance, your code
uses an XML file from a third-party web site, but that file isn't valid XML. Or, a person has tried
to join your site, but has requested a username already in use.

Resource failures

Your code is failing because of a breakdown in communication with another process. For
instance, you cannot connect to a third-party web site because of network traffic. Or, your
database is refusing additional connections because it is overloaded.

Unlike Java, PHP does not use exceptions internally. Therefore, many programming errors still need
to be trapped in the traditional manner. You can then choose to either handle them in place or create
an exception for them.
Client code errors are also highly dependent on the extension. Many extensions also do not throw
exceptions or they throw them only when you use their object-oriented interface instead of their
procedural interface. However, it makes sense to use exceptions when dealing with client code errors.

Most PHP programs don't try to recover from resource failures. If a web site or database doesn't
respond the first time, it's usually assumed to be down. Sometimes it makes sense to retry the
connection, but that can often be handled more easily without exceptions.

Exceptions are not an object-oriented version of goto. They're not for flow control; they're for
handling exceptional events. In addition to any other goto evils, exceptions also have unnecessary
overhead compared to alternative, and superior, methods, such as a while loop.

7.5.2 Throwing an Exception


Example 7-6 is part of a larger user registration system. It tries to insert a new user into the
database, but throws an exception if there's already someone in the table with the same username.

Example 7-6. Inserting new users into a member database

// Add user to database, throw exception if duplicate entry

function addUser($db, $user) {

$db->query("INSERT INTO users

VALUES ('${user[0]}', '${user[1]}');");

// Error code 19 means INSERT violates UNIQUEness constraint

if ($db->lastError( ) = = 19) {

throw new Exception("${user[0]} already in database.", 19);

// First element is username, second is password

$user = array('rasmus', 'z.8cMpdFbNAPw');

try {
$db = new SQLiteDatabase('address-book.db');

addUser($db, $user);

} catch (Exception $error) {

print "Message: ".$error->getMessage( )."\n";

print "Error Code: ".$error->getCode( )."\n";

When there's a username collision, you get:

Message: rasmus already in database.

Error Code: 19

Assume the user table's first field was declared UNIQUE during table creation. Therefore, when
there's already a rasmus user in the database, trying to insert a second rasmus violates this
constraint. This causes SQLite's lastError( ) method to return 19.

While SQLite's constructor throws exceptions, the query( ) method does not. Therefore, you must
manually trigger the exception by creating a new Exception object and passing a message and error
code.

The Exception class stores the constructor's first and second parameters in getMessage( ) and
getCode( ). The first parameter, the message, is a string; the second, the error code, must be an
integer. Trying to pass a non-number as the second argument causes PHP to complain with a fatal
error.

Use the throw keyword to throw an Exception object. In this example, the object is instantiated and
thrown in one line, but these operations can occur separately.

The exception isn't caught inside addUser( ), so it bubbles up a level. However, since the call to
addUser( ) is wrapped inside a try/catch block, it's caught and handled there. This is a perfect
example of placing the dirty job of checking return values inside a function, thus freeing up the core
application logic to use exceptions.

In Example 7-6, the exception handling is minimal. All it does is print out two lines of debugging
information. In a full system, you should make the exception trigger a page that requests that the
person select a different username. This wasn't demonstrated here, but it's a nicer way to handle
problems than just dying.

7.5.3 Writing and Catching Custom Exceptions


There are two good reasons to define custom exception classes: to customize your processing of
different errors and to embed additional information inside your exception objects.
7.5.3.1 Handling multiple exception types

Besides throwing the default Exception class, you can create and throw custom exception objects by
extending Exception. For instance, Example 7-7 throws a duplicateUsernameException exception.

Example 7-7. Catching a custom exception

class duplicateUsernameException extends Exception { };

// Add user to database, throw exception if duplicate entry

function addUser($db, $user) {

$db->query("INSERT INTO users

VALUES ('${user[0]}', '${user[1]}');");

// Error code 19 means INSERT violates UNIQUEness constraint

if ($db->lastError( ) = = 19) {

throw new duplicateUsernameException(

"${user[0]} already in database.", 19);

// First element is username, second is password

$user = array('rasmus', 'z.8cMpdFbNAPw');

try {

$db = new SQLiteDatabase('address-book.db');

addUser($db, $user);

} catch (duplicateUsernameException $error) {


print "Message: ".$error->getMessage( )."\n";

print "Error Code: ".$error->getCode( )."\n";

The methods and properties of duplicateUsernameException are identical to its parent, but by
subclassing Exception you can catch this error separately and handle it accordingly. This is a good
reason to subclass Exception.

To handle the different types of exceptions, define multiplecatch blocks. When PHP detects a thrown
exception, it works its way down the list of catches. For each block, PHP compares the exception
type against the class name using an instanceof check. PHP uses the first matching block.

Therefore, you must catch a duplicateUsernameException before an Exception. Since


duplicateUsernameException is a subclass of Exception, switching the order results in all
exceptions being caught in the first block.

For instance, Example 7-8 continues to throw duplicateUsernameException as before, but also
remembers that SQLite throws an SQLiteException from its constructor.

Example 7-8. Catching multiple exceptions

class duplicateUsernameException extends Exception { };

// Add user to database, throw exception if duplicate entry

function addUser($db, $user) {

$db->query("INSERT INTO users

VALUES ('${user[0]}', '${user[1]}');");

// Error code 19 means INSERT violates UNIQUEness constraint

// Throw duplicateUsernameException

if ($db->lastError( ) = = 19) {

throw new duplicateUsernameException(

"${user[0]} already in database.", 19);

}
// First element is username, second is password

$user = array('rasmus', 'z.8cMpdFbNAPw');

try {

$db = new SQLiteDatabase('address-book.db');

addUser($db, $user);

} catch (duplicateUsernameException $error) {

// This function not implemented

requestDifferentUsername($user);

} catch (SQLiteException $error) {

print "Message: ".$error->getMessage( )."\n";

print "Error Code: ".$error->getCode( )."\n";

In this example, a duplicateUsernameException error is thrown if the username is already in the


database. Since that's only a minor error, call requestDifferentUsername( ) to recover gracefully.

A failure in the constructor is more severe. Since that's of type SQLiteException, it's trapped in a
separate catch block, where you can handle it accordingly.

Without subclassing Exception, you need to place a giant switch inside catch and check the type of
each error. This is ugly and doesn't scale well. Additionally, one pattern of object-oriented
programming is replacing the switch statements that check an object's type with different
subclassed objects.

7.5.3.2 Embedding custom exception data

Another reason to subclass Exception is to add custom methods:

class duplicateUsernameException extends Exception {

private $username;
public function _ _construct($username) {

parent::_ _construct( );

$this->username = $username;

public function getUsername( ) {

return $this->username;

public function getAlternativeNames( ) {

// return an array of similar names, like "rasmus34"

The getAlternativeNames( ) method returns an array of similar names that the application can
pass on to the person trying to sign up. You can integrate this method into your code:

// First element is username, second is password

$user = array('rasmus', 'z.8cMpdFbNAPw');

try {

$db = new SQLiteDatabase('address-book.db');

addUser($db, $user);

} catch (duplicateUsernameException $error) {

requestDifferentUsername($error);

} catch (Exception $error) {

print "Message: ".$error->getMessage( )."\n";


print "Error Code: ".$error->getCode( )."\n";

Now the requestDifferentUsername( ) function is passed an object it can use to discover both the
original username and a list of valid alternatives.

< Day Day Up >


< Day Day Up >

7.6 Setting a Custom Exception Handler


PHP always dies with a fatal error whenever you fail to catch an exception. However, you can control
what PHP does before it dies by creating a custom exception-handling function and registering it
using set_exception_handler( ). For example:

function my_exception_handler($e) {

error_log('Error in file ' . $e->getFile( ) . ' on line ' .

$e->getLine( ) . ".\n" . 'Error message: "' . $e->getMessage( ) .

'" and error code: ' . $e->getCode( ) . '.');

set_exception_handler('my_exception_handler');

$version = '1.0';

$element = '&'; // & is an ILLEGAL element name

$dom = new DOMDocument($version);

$ab = new DOMElement($element);

$ab = $dom->appendChild($ab);

Error in file /www/www.example.com/dom.php on line 14.

Error message: "Invalid Character Error" and error code: 5.

Because the exception isn't caught inside a catch block, the exception handler kicks in and prints the
message to the error log. When the function finishes, PHP dies.

If you're writing new PHP 5 code, there's really no reason to use set_exception_handler( ),
because you can just wrap the main section of your code inside atry/catch block. However, you
may find it useful when converting older PHP 4 code that wasn't designed with exception handling in
mind over to PHP 5.

In particular, if you're using set_error_handler( ), adding a call to set_exception_handler( )


may be a good stop-gap method. It would allow you to handle exceptions like you're already handling
errors, without being forced to restructure your code.

< Day Day Up >


< Day Day Up >

7.7 Processing Errors with a Custom Handler


In PHP 4, you can enable a custom error handler for all errors by callingset_error_handler( ).
PHP 5 lets you refine that behavior by allowing you to specify which types of errors the handler
should process.

To restrict set_error_handler( ) to a subset of errors, pass it a second argument. For example:

set_error_handler('my_error_handler', E_NOTICE);

function my_error_handler($errno, $errstr, $errfile, $errline) {

print "A notice occurred\n";

$a++;

A notice occurred

This example sets my_error_handler( ) as the handler for E_NOTICE errors, but lets PHP handle all
other error types. Since incrementing an undefined variable triggers a notice, PHP invokes the
function.

If the custom error handler returns false, then PHP also does its own set of error handling, in
addition to whatever you code inside your handler. For instance:

set_error_handler('my_error_handler', E_NOTICE);

function my_error_handler($errno, $errstr, $errfile, $errline) {

print "A notice occurred\n";

return false;

}
$a++;

PHP Notice: Undefined variable: a...

A notice occurred

This example is identical to the last one, except that my_error_handler( ) now returns false. As a
result, you get two sets of messages: PHP's and yours.

< Day Day Up >


< Day Day Up >

7.8 Debugging Functions


Debugging is the bane of every programmer. When you're trying to untangle why a variable isn't set
correctly inside a function-which was itself called from another function, which was included from an
include file, which was wrapped inside a class-it can be maddening to track down where in the chain
of events everything started to go wrong.

PHP 5 includes a set of functions that ease your pain. The debug_backtrace( ) and
debug_print_backtrace( ) functions return and print, respectively, an assortment of information
about the current function and every previous function in the call stack.

7.8.1 Integrating Debugging into Your Code


One good strategy for deploying debug_print_backtrace( ) is to define( ) a DEBUG constant
whose value determines whether your code should run in debugging mode.Example 7-9 is a modified
version of Example 7-8 with integrated debugging scaffolding.

Example 7-9. Integrating backtrace debugging

// Enable debug mode;

// Can be placed in a file that's added using the

// auto_prepend_file configuration directive

define('DEBUG', true);

class duplicateUsernameException extends Exception { };

// Add user to database, throw exception if duplicate entry

function addUser($db, $user) {

$db->query("INSERT INTO users

VALUES ('${user[0]}', '${user[1]}');");


// Error code 19 means INSERT violates UNIQUEness constraint

// Throw duplicateUsernameException

if ($db->lastError( ) = = 19) {

throw new duplicateUsernameException(

"${user[0]} already in database.", 19);

// Another error, like a malformed SQL statement

if (DEBUG && $db->lastError( )) {

debug_print_backtrace( );

At the top of the script, DEBUG is defined to true. This enables debug mode. Using a constant instead
of a variable prevents a script from modifying the value. Another advantage is that constants have
global scope, which means you can use them inside of functions and classes without declaring them
global. (Forgetting to reference the global $debug variable results in a bug in the bug-catching code,
which is quadruply frustrating.)

A good place to define DEBUG is in a file that's automatically included at the top of every file using the
auto_prepend_file configuration directive. This ensures that you won't forget to set a value for the
constant.

Underneath the check to throw a duplicateUsernameException inside addUser( ) is:

if (DEBUG && $db->lastError( )) {

debug_print_backtrace( );

This block causes PHP to print out debugging information and helps you locate the error.

The debug_print_backtrace( ) function is always available, but if you're using exceptions, you can
also call the getTraceAsString( ) method, which returns equivalent results:

// Another error, like a malformed SQL statement

// Throw Exception
if ($db->lastError( )) {

$e = new Exception(

sqlite_error_string($db->lastError( )),

$db->lastError( ));

if (DEBUG) { print $e->getTraceAsString( ) . "\n"; }

throw $e;

Example 7-10 works correctly when you test it, but triggers an exception for some (but not all)
people trying to sign up for your site.

Example 7-10. Inserting new users incorrectly

// Can be placed in a file that's added using

// auto_prepend_file configuration directive

define('DEBUG', true);

class duplicateUsernameException extends Exception { };

// Add user to database, throw exception if duplicate entry

function addUser($db, $user) {

$db->query("INSERT INTO users

VALUES ('${user[0]}', '${user[1]}');");

// Error code 19 means INSERT violates UNIQUEness constraint

// Throw duplicateUsernameException

if ($db->lastError( ) = = 19) {

throw new duplicateUsernameException(


"${user[0]} already in database.", 19);

// Another error, like a malformed SQL statement

if (DEBUG && $db->lastError( )) {

print sqlite_error_string($db->lastError( ));

$user = array($_GET['username'], $_GET['password']);

try {

$db = new SQLiteDatabase('address-book.db');

addUser($db, $user);

} catch (duplicateUsernameException $error) {

requestDifferentUsername($user);

} catch (SQLiteException $error) {

print "Message: ".$error->getMessage( )."\n";

print "Error Code: ".$error->getCode( )."\n";

The SQLite error message for this bug, as reported by sqlite_error_string($db->lastError( ),


is:

SQL logic error or missing database

The SQLite error message doesn't provide you with much to go on. You know the database exists
because you see it on the filesystem. You also know it's not a permissions problem because you're
accessing the database in the same manner as everyone else. Besides, it works for most people, so
it's not like the code is completely broken.
Eventually, you find a case that triggers debug_print_backtrace( ). Here's the output from the
function:

#0 addUser(SQLiteDatabase Object ( ), Array ([0] => rasmus,[1] => sec'ret%))

called at [/www/www.example.com/sqlite.php:31]

The username looks okay, but maybe there's something wrong with the password. Aha! It contains a
single quotation mark ('), and you're not calling sqlite_escape_string( ) on the input. In addition
to a security vulnerability, this causes an SQL parse error because yourINSERT statement terminates
early.

The fix is simple. You must escape the values before passing them to SQLite, as shown inExample 7-
11.

Example 7-11. Inserting new users correctly

// Add user to database, throw exception if duplicate entry

function addUser($db, $user) {

// Escape input parameters

foreach ($user as &$value) {

$value = sqlite_escape_string($value);

$db->query("INSERT INTO users

VALUES ('${user[0]}', '${user[1]}');");

// Error code 19 means INSERT violates UNIQUEness constraint

// Throw duplicateUsernameException

if ($db->lastError( ) = = 19) {

throw new duplicateUsernameException(

"${user[0]} already in database.", 19);

}
// Another error, like a malformed SQL statement

if (DEBUG && $db->lastError( )) {

debug_print_backtrace( );

The foreach loop cycles through the elements of $user using a new PHP 5 feature: iteration by
reference. Placing an ampersand (&) before $value makes foreach provide you with a reference to
array elements. This allows you to reassign the output of sqlite_escape_string( ) to $value
instead of $user[$key].

7.8.2 Redirecting Output to a File


Use PHP's output-buffering functions to redirect the results of debug_print_backtrace( ) to the
error log or a file:

// Another error, like a malformed SQL statement

if (DEBUG && $db->lastError( )) {

$ob = ob_start( );

debug_print_backtrace( );

error_log(ob_get_clean( ));

This creates an output buffer, $ob, and places the contents of debug_print_backtrace( ) inside it.
The buffer is then flushed to the error log using ob_get_clean( ).

< Day Day Up >


< Day Day Up >

Chapter 8. Streams, Wrappers, and Filters


PHP's streams provide a file-like interface for reading and writing data. A single interface for many
different data sources lets you write reusable code: a program that extracts data from a local file can
just as easily extract data from a web page, and new streams data sources can immediately be used
with any existing streams code. This reuse makes streams a powerful and underused feature in PHP.

Streams were a late addition to PHP 4, arriving only in PHP 4.3. As a result, they're barely covered in
other PHP books, and hardly anyone uses them to their full extent. Therefore, while PHP 5 introduces
some new streams features, that's not the focus of this chapter. Instead, it compares programming
with and without streams and discusses the advantages streams provide over alternatives, such as
cURL.

An extension that provides a streams interface to a new data source is called awrapper. The streams
interface consists of 14 methods, but you need only implement the methods that make sense for
your data source. For example, if you wanted to provide a streams interface to a mailbox of
messages, you might not support renaming or rewriting. You can write wrappers in C or in PHP.

The SimpleXML extension is written to use the streams interface for its data. This means that the
same function that loads XML from a datafile (simplexml_load_file( )) also loads XML from a URL,
an FTP site, and any other data source you have a wrapper for.

Sometimes, in addition to transporting data, you want to modify it. For example, you might want to
encode HTML entities, strip HTML tags, or otherwise alter the text. Since these conversions are
independent of the wrapper, it doesn't make sense for the wrapper to handle the job. Stripping HTML
tags is something you may want to do for local files and text retrieved via HTTP, and there's no point
in writing one function for each location.

PHP streams support filters to make modifications to a stream's data. Once installed, afilter sits
between a streams wrapper and PHP code that reads from or writes to a stream. The filter intercepts
the data and may modify it. This makes a filter's actions transparent to a wrapper and to user code,
allowing you to associate a filter with any stream, regardless of the wrapper the stream is using.

While it makes sense only to have one wrapper per data source, you can chain multiple filters in a
row. The Unix shell has a similarly powerful feature, pipelines, where you can connect a series of
programs to modify data. In Unix, as in PHP streams, all a filter knows is that it must take the data
provided as input, filter it according to some rules, and then provide the new data as output. It
doesn't care if this information has already been filtered or if another filter or two follows it.

This chapter shows how to use streams by explaining the wrappers available with PHP 5:file, http,
ftp, and php. php is a PHP-specific wrapper that handles information passed to and emitted from
PHP using standard input, output, and error. There are also a few compression wrappers and SSL-
enabled wrappers that are available depending upon how your version of PHP is configured.

Next, you learn how to write a wrapper of your own so you can extend PHP. The Section 8.4 shows
how to build a shared memory wrapper that uses PHP's shared memory functions. Shared memory
allows you to easily communicate among all PHP processes on the same web server in an efficient
manner.

After wrappers come filters. Again, PHP has a few built-in filters. In particular, the
string.strip_tags and convert.iconv filters are extremely useful, as they allow you to eliminate
HTML tags and convert text from one character encoding to another, respectively.

However, the main power of filters comes from the ability to write your own filters using PHP. Since
filters are so flexible, there's a bit of wrapping code necessary to implement even a very simple filter,
but once the basic structure is in place, it's easy to modify what you have in all sorts of directions.

This chapter concludes by showing how to write custom filters, using thehtmlentities( ) function
as an example, and also demonstrates how to chain multiple filters together.

For a complete streams reference, see the "Streams" section of the PHP Manual at
http://www.php.net/stream. If you're interested in implementing wrappers and filters in C, read
http://www.php.net/streams.

< Day Day Up >


< Day Day Up >

8.1 Using the Streams API


This section covers reading and writing files using the streams functions. For now, the examples only
use the file wrapper, which is for local files. Other wrappers, along with their different configuration
options, are covered in the next section. However, due to the miracle of streams, you won't need to
learn any new functions to use those new wrappers.

The syntax for specifying a stream is scheme://target. The scheme can be the name of any installed
wrapper, such as file or http. The target varies depending upon the wrapper, but is usually a path
to the file, with an optional hostname if applicable. For instance, to reference the local file
/www/www.example.com/index.html, use file:///www/www.example.com/index.html.

The best way to read the entire contents of a stream into a variable is the file_get_contents( )
function. For example:

// get the index page

$index = file_get_contents('file:///www/www.example.com/index.html');

// short hand syntax for above

$index = file_get_contents('/www/www.example.com/index.html');

These examples locate the file /www/www.example.com/index.html on the hard drive, read its
contents, and place them in the $index variable. Since the file wrapper is the default wrapper for
file_get_contents( ), you can omit it for brevity.

You can make file_get_contents( ) also search your include_path. To enable this feature, pass
true as the second argument:

$classes = file_get_contents('classes.php', true);

Now instead of looking only in the current directory, file_get_contents( ) also works through the
include hierarchy, so its behavior is identical to include.

The file_get_contents( ) function respects read permissions and PHP's internal access
restrictions, such as open_basedir. Trying to read in a restricted file causes PHP to produce a
warning:

$forbidden = file_get_contents("/etc/shadow");

PHP Warning: file_get_contents(/etc/shadow): failed to open stream:


Permission denied

In these cases, file_get_contents( ) returns false.

While file_get_contents( ) is the most efficient way to retrieve an entire file, a large file will use
up lots of memory. If you don't need to access the entire file at once, usefopen( ) instead:

$stream = 'file:///www/www.example.com/index.html';

$handle = fopen($stream, 'rb');

$contents = '';

while (!feof($handle)) {

$data = fread($handle, 8192);

print $data; // or another function to handle the data

fclose($handle);

The fopen( ) function returns a file handle, or file pointer, to the file. Passing this handle to file
functions such as feof( ), fread( ), and fclose( ) makes them act upon your file.

This code, however, is far more complex because you need to worry about opening the file for read
access and then looping through its contents one chunk at a time. You also need to check repeatedly
if you've read the entire file. Therefore, unless you're manipulating very large files on a regular basis,
it's best to always use file_get_contents( ).

When you need to do the reverse (save a variable as a file), use file_put_contents( ):

file_put_contents('file:///www/www.example.com/index.html', $index);

file_put_contents('/www/www.example.com/index.html', $index);

These two functions place the contents of $index into /www/www.example.com/index.html.

The default behavior is to overwrite an existing file; however, you can makefile_get_contents( )
append to a file by passing the FILE_APPEND constant as a third parameter:

file_put_contents('/var/log/http/error_log', $error, FILE_APPEND);

This adds the contents of $error to the end of /var/log/http/error_log.


An alternative method of sending data is fwrite( ):

$stream = '/www/www.example.com/index.html';

$handle = fopen($stream, 'wb');

fwrite($handle, $index);

fclose($handle);

This opens the stream for writing and sends the contents of $index to the file
/www/www.example.com/index.html.

< Day Day Up >


< Day Day Up >

8.2 Wrapper Overview


As long as there's a wrapper that supports it, you can use streams to read from and write to any
location or protocol using the same set of file manipulation functions from the previous section.

For example, here are a few streams that access nonlocal files:

http://www.example.com/index.html

https://secure.example.com/secret.html

ftp://user:[email protected]/www/www.example.com/index.html

The first stream makes an HTTP request to www.example.com and retrieves the file from the
Internet. The second makes a secure HTTP connection using SSL and accesses secret.html from
secure.example.com. The final wrapper transfers a file using FTP, passing along a username and
password in the process.

However, while streams do abstract away most implementation specifics, they can't alter the
inherent nature of the protocol itself. For instance, although it's quite easy to read files using HTTP,
most web servers are not set up to allow you to save files using HTTP. Likewise, when you use FTP,
you need to provide a username and password or the FTP server won't be able to authenticate you as
a legitimate user.

8.2.1 Bundled Wrappers


Before getting into the specifics of each individual wrapper, here's a brief overview of all the wrappers
bundled with PHP 5, as well as the additional wrappers you can enable with certain extensions.

PHP 5 automatically provides you with four wrappers:

file

Talks to the local filesystem

http

Requests a file from a web server


ftp

Reads and writes files from an FTP server

php

Handles reading from standard in and writing to standard out and error.

Compiling in certain extensions enables four additional wrappers:

compress.bzip2

Automatically (de)compresses bzipped files on the fly; requiresBZip2 support

compress.zlib

Automatically (de)compresses gzipped files on the fly; requireszlib support

ftps

Securely reads and writes files from an FTP server; requires OpenSSL

https

Securely reads files from an HTTP server; requires OpenSSL

During the configuration process, enable bzip2 compression support with --with-bz2, zlib
compression with --with-zlib, and OpenSSL with --with-openssl.

The stream_get_wrappers( ) function returns an array of available wrappers. Use this to discover
which filters are available on your machine:

print_r(stream_get_wrappers( ));

which prints:

Array

[0] => php

[1] => file


[2] => http

[3] => ftp

[4] => compress.zlib

This copy of PHP has the basic set of wrappers and the zlib wrapper, but not the extra SSL or bzip2
wrappers.

< Day Day Up >


< Day Day Up >

8.3 Wrapper Details


The following sections show how to read from and write to different locations using PHP 5's bundled
wrappers. You've already seen how to process files stored on your server's hard drive. Now you use
the http and ftp wrappers to access remote files using HTTP and FTP.

The http and ftp wrappers require the allow_url_fopen configuration


directive set to On. This is the default setting, but allow_url_fopen may be
turned off on some systems because it allows crackers to more easily exploit
insecurely written code.

Specifically, when allow_url_fopen is on, include $file will even include


remote files. If a cracker is somehow able to set $file to a value of her own,
she can make your web server execute any PHP code she wants just by posting
it on a web site and setting $file to that URL.

Another wrapper you'll learn how to use is the php wrapper for standard in, out, and error. This
wrapper is particularly useful for people using PHP on the command line or with web services.

The final set of wrappers are the compression wrappers. These wrappers are similar to thefile
wrapper, except that they let you read and write data that is compressed usinggzip and bzip2.

8.3.1 HTTP and HTTPS


While file_get_contents( ) and file_put_contents( ) may seem to work only for local files,
they're actually valid for any stream that enables them. Since PHP comes with an http stream, this
means you can retrieve files using HTTP.

For example, you can fetch index.php from www.example.com:

$index = file_get_contents('http://www.example.com/index.php');

This tells PHP to use the http wrapper to retrieve the target www.example.com/index.php. The
wrapper knows it should make an HTTP GET request of the web server located at www.example.com.

You can also pass along a username and password to be used as HTTP Basic Authentication
credentials, like this:

$index = file_get_contents('http://rasmus:[email protected]/index.php');

The username comes immediately after the double slashes and is separated from the password by a
colon. After the password, place an @.

Modify the HTTP request by passing a stream context to file_get_contents( ). A stream context is
a way to tweak the default behavior associated with a wrapper. For example, thehttp wrapper
allows you to modify the HTTP method from GET to POST or add HTTP headers such as cookies.

Here's how to add a cookie to your request using streams and file_get_contents( ):

$options = array(

'http' => array(

'header' => "Cookie: username=rasmus\r\n"

);

$context = stream_context_create($options);

$index = file_get_contents('http://www.example.com/index.php',

false, $context);

The stream_context_create( ) function takes an array of options and returns a context resource.
Use the wrapper name as the array key. In this example it's http. These elements hold another
array that specifies the wrapper options.

Each wrapper has its own set of options that vary depending upon what's appropriate for the
wrapper. The http wrapper has a header option where you can enter data to be added to the HTTP
header of your request.

The text Cookie: username=rasmus\r\n sets a cookie named username to the value rasmus. Every
HTTP header needs to begin with a header name (Cookie), followed by a colon and a space, the
header value, and then \r\n.

Pass the context as the third parameter to file_get_contents( ) and set the second parameter to
false. The second parameter controls whether file_get_contents( ) should search the
include_path, an option that makes no sense for the http wrapper.

Now when the web server on www.example.com processes the request, it will pass along your
cookie.

Compiling PHP with the --with-openssl option enables an https wrapper. Its behavior is identical to
the http wrapper, except that it uses SSL.

Table 8-1 has a list of all http and popular https context options.
Table 8-1. http and https wrapper context options

Requires
Name Description Default
https?
Allow self-signed SSL certificates; value
allow_self_signed false Yes
must be true or false

Path to certificate authority file used to


cafile authenticate remote peers; used when Yes
verify_peer is true

HTTP header, such as Cookie and


header No
Content-length

method HTTP method, such as GET and POST GET No

proxy Proxy server URL No

Send requests as full instead of relative


request_fulluri URLs (necessary for some broken proxy false No
servers); value must be true or false

Verify SSL certificate; value must be


verify_peer false Yes
true or false

Value in user_agent
HTTP User-agent header, overridden by
user_agent configuration No
header option
directive

Additional SSL context options are located at http://www.php.net/transports.

Since HTTP does not distinguish between getting and putting information on a protocol level, you
cannot use file_put_contents( ) with the http wrapper. However, you can make an HTTP POST
or PUT using http_get_contents( ). Here's an example of an HTTP POST request:

$post_variables = array('usernamed' => 'rasmus');

$content = http_build_query($post_variables);

$content_length = strlen($content);

$options = array(

'http'=>array(

'method' => 'POST',

'header' =>

"Content-type: application/x-www-form-urlencoded\r\n" .
"Content-length: $content_length\r\n",

'content' => $content

);

$context = stream_context_create($options);

$index = file_get_contents('http://www.example.com/index.php', false, $context);

This code makes an HTTP POST request and sends along form data that sets the username variable
equal to rasmus.

The new PHP 5 function http_build_query( ) then iterates through all the elements in the
$post_variables array and encodes with urlencode( ) to ensure they're valid inside an HTTP
request. Variables are then joined with their values using an equals sign (=) and then linked all
together using ampersand (&).

This conversion is similar to what's done for an HTTP GET request. One difference between GET and
POST, however, is that you must calculate your data's content length and pass that along as an HTTP
header. This is done using strlen( ).

Now you have all the data necessary to create the stream context. The method is set to POST; the
header is has two lines, a Content-type and a Content-length; and the content is set to
$content, your encoded data. All HTTP POST forms must set a Content-type of application/x-
www-form-urlencoded so the web server will know how to interpret the data.

This causes the $_POST array of index.php to contain your form data. Calling print_r($_POST) for
this request gives you:

Array

[username] => rasmus

There's one value, username, and it's set to rasmus.

One benefit of using streams over an extension like cURL is that you're guaranteed that the http
wrapper is always available. Since cURL requires an external library, it's not enabled on all copies of
PHP.
8.3.2 FTP and FTPS
The ftp wrapper allows you to speak to FTP servers. This wrapper behaves similarly to thehttp
wrapper:

$index = file_get_contents(

'ftp://rasmus:[email protected]/www/www.example.com/index.html');

This logs into ftp.example.com as user rasmus and retrieves the index.html file in the
/www/www.example.com/ directory.

If a username or password is not provided, PHP attempts to log in as user anonymous.

You can save a file to an FTP server using file_put_contents( ). This command saves $index to
the server:

file_put_contents('ftp://rasmus:[email protected]/www/www.example.com/

index.html', $index);

By default, if a file already exists on the FTP server, PHP will not overwrite it. Set theoverwrite
context option to change this behavior, like so:

$options = array(

'ftp' => array(

'overwrite' => true

);

$context = stream_context_create($options);

file_put_contents(

'ftp://rasmus:[email protected]/www/www.example.com/index.html',

$index, false, $context);

If the ftp overwrite option is set to true, the stream won't issue an error when there's a file at the
location where you're saving the stream. Failing to do this causesfile_put_contents( ) to return
NULL and issue a warning:

PHP Warning:
file_put_contents(ftp://[email protected]/www/www.example.com/index.html):

failed to open stream: Cannot overwrite files in current mode

FTP server reports 213 3

As with the http wrapper, there's an ftps wrapper that is an SSL version of ftp.

8.3.3 PHP Input and Output


The php wrapper is particularly useful for people using PHP on the command line or with web services
because it allows you to access any information sent to PHP from another program, regardless of
format.

Normally, you can rely on the superglobal variables to contain all the data passed to PHP. HTTP GET
and POST variables are in $_GET and $_POST, cookies are in $_COOKIES, and file uploads are in
$_FILES. However, this happens only because this information is specially structured to be parsed
into variables. When PHP is sent data in other formats, such as a SOAP packet that's structured as
XML, the superglobal variables are not helpful at all. Similarly, when you use PHP to write a
command-line tool, you communicate with the user through standard input instead of the
superglobals.

The php wrapper is also handy for sending data from PHP. This wrapper makes it easy to send output
to multiple locations in a consistent fashion. You can write to both standard out and standard error or
control whether your data goes to the screen, a file, or both.

8.3.3.1 Standard input, output, and error

When you use PHP on the command line, it reads and writes information to and from three different
locations. These places are known as standard input, standard output, and standard error.

Standard input is all the data sent to PHP, and standard output is the data coming from PHP.
Standard input and output are typically abbreviated as stdin and stdout. These are the names used
by the php filter.

This command makes the shell pass the contents of input.txt to the PHP script wordwrap.php on
standard input and stores PHP's standard output in output.txt:

$ php wordwrap.php < input.txt > output.txt

Inside your script, however, you don't know about input.txt and output.txt. You only know that
you're to process data from php://stdin and print the results to php://stdout. This allows you to
create a generalized script that works in conjunction with other command-line tools.

To read from standard input, do this:

$stdin = file_get_contents('php://stdin');
To write to standard output, do this:

file_put_contents('php://stdout', $data);

This simple script rewraps text to 10 characters:

$stdin = file_get_contents('php://stdin');

$data = wordwrap($stdin, 10);

file_put_contents('php://stdout', $data);

When input.txt contains:

Hello! My name is Rasmus Lerdorf.

the file output.txt ends up as:

Hello! My

name is

Rasmus

Lerdorf.

Standard error is similar to standard output, but it's a special channel for error messages.It's often
shortened as stderr, so to write to standard error, do this:

file_put_contents('php://stderr', $errors);

Standard error is used when you're storing standard output to a file and want to see warning
messages on the console. Think of standard error as error_log( ) for the command line. For
instance, whenever standard input is empty, Example 8-1 reports a warning using standard error.

Example 8-1. Wrapping standard input

$stdin = file_get_contents('php://stdin');

if (empty($stdin)) {

file_put_contents('php://stderr', "wordwrap.php received no data.\n");


$data = '';

} else {

$data = wordwrap($stdin, 10);

file_put_contents('php://stdout', $data);

Now if you process blank.txt, a file without any data, you're given the following warning:

% php wordwrap.php < blank.txt > output.txt

wordwrap.php received no data.

You're not allowed to write to standard input, nor can you read from standard output or standard
error-these streams work in only one direction. Writing to standard input won't write any bytes,
which leads to an (inaccurate) warning message from PHP:

PHP Warning: file_put_contents( ): Only 0 of 1 bytes written, possibly out of free

disk space.

Reading from standard output or error won't generate an error, but these streams never return any
data, so there's really no point.

Also, PHP has its own set of output buffering functions that let you capture printed data. Writing to
standard output circumvents these buffers, so be sure you really want standard output before you
use this target.

To ensure you send data to the current PHP output buffer, use the output target instead. More
details on output appear in the next section.

8.3.3.2 General input and output

While standard input and output are perfect for command-line tools, most people use PHP in
combination with a web server to process requests. In these cases, you should use another set of
streams instead.

The php://input stream contains data sent in using HTTP POST. This is identical to what's stored in
the $HTTP_RAW_POST_DATA variable, but it's always available, regardless of the setting of the
always_populate_raw_post_data configuration directive. Using php://input is also more efficient
than $HTTP_RAW_POST_DATA.

This filter is most commonly used for processing SOAP requests. For example, this simple SOAP
server code for a Weather_Server service:

require 'SOAP/Server.php';

// Your Web Service class

require 'WebServices/Weather_Server.php';

$server = new SOAP_Server;

$server->addObjectMap(new Weather_Server,

'http://www.example.com/weather');

$server->service(file_get_contents('php://input'));

The complement to php://input is php://output. This stream is similar to standard output, but it
respects your PHP output buffering settings. Therefore, unless you explicitly want standard out, you
should use php://output as a stream alternative to print. This holds true even if you ultimately
send the results to standard output. For example:

$ob = ob_start( ); // Start buffering of output

print '1'; // Captured by buffer

file_put_contents('php://stdout', '2'); // Direct to standard out

file_put_contents('php://output', '3'); // Captured by buffer

ob_implicit_flush($ob); // Flush buffer contents

213

This snippet creates a PHP output buffer and then prints 1, 2, and 3. Since the output of print and
file_put_contents('php://output') are captured by the buffer, they appear after the number 2.
This happens even though ob_implicit_flush( ) sends data to standard out when used on the
command line, because writing directly to php://stdout bypasses the buffering scheme.

The php://output filter is also useful when you want to write a generic function that sends data to
either the screen or a file:

function log_error($stream, $message) {

$time = strftime('%c'); // default time reprentation

fwrite($stream, "Error occured at time: $time\n");


fwrite($stream, $message);

$screen = fopen('php://output', 'w+');

log_error($screen, "Cannot connect to database.");

fclose($screen);

The log_error( ) function prints the current time and logs the error. In this case, it's sent through
the standard output buffer, but you could also send the message to a file. All you need to do is open
a different stream and pass that into log_error( ). Without php://output, you would need two
separate pieces of code to handle both cases.

8.3.3.3 Filtered input and output

There's one final php wrapper, php://filter. This isn't so much a wrapper as a way to use filters
with stream functions that don't let you attach a filter before reading data.

Normally, you need to tell PHP that you want to filter a stream after you open the stream but before
you read from or write to it. However, some functions-for example, file_get_contents( )- b o t h
open and read in the file in one step. Therefore, you're unable to attach the filter to the stream.

The php://filter wrapper is a way around this problem. The exact details of its use are explained in
Section 8.5.3, later in this chapter.

8.3.4 Compression
The compression wrappers allow you to automatically compress and decompress files on the hard
drive. The compress.zlib filter handles gzip, and the compress.bzip2 filter handles bzip2.

To use the compress.zlib wrapper, you must configure PHP with the --with-zlib flag, whereas
compress.bzip2 needs the --with-bzip2 flag. While the primary applications of these libraries are
to the zlib and bzip2 extensions, you also automatically get the wrappers.

Use them just like the file wrapper:

$log = file_get_contents('compress.zlib:///var/log/access.log.gz');

This uncompresses /var/log/access.log.gz and returns it to $log.

Or you can use the compress.bzip2 wrapper:

$log = file_get_contents('compress.zlib:///var/log/access.log.bz2');
Both wrappers also allow you to write files:

file_put_contents('compress.zlib:///pub/php/php-5.0.0.gz', $php);

The compression wrappers work only on local files; you cannot combine them with thehttp wrapper
to open compressed files via HTTP.

< Day Day Up >


< Day Day Up >

8.4 Creating Wrappers


You can write custom wrappers in PHP. This lets you extend PHP to "speak" any protocol you want
while still letting you use the standard stream functions, such asfile_get_contents( ).

Stream wrappers are implemented as a class with specific methods. These methods are called by the
stream-handling code when a stream using your wrapper is opened, read from, written to, and so
forth.

In all, there are 17 potential methods that make up a complete wrapper, but it's rare to actually code
all of these. Table 8-2 contains a list of wrapper methods and when they're evoked.

Table 8-2. Wrapper methods

PHP 5
Method Function
only?
boolean stream_open(string path, string mode, int
fopen( ) No
options, string opened_path)
void stream_close(void) fclose( ) No
fread( ), fgets(
string stream_read(int count) No
)

fwrite( ),
int stream_write(string data) No
fputs( )

boolean stream_eof(void) feof( ) No


int stream_tell(void) ftell( ) No
boolean stream_seek(int offset, int whence) fseek( ) No
boolean stream_flush(void) fflush( ) No
array stream_stat(void) fstat( ) No
boolean unlink(string path) unlink( ) Yes
boolean mkdir(string path, int mode, int options) mkdir( ) Yes
boolean rmdir(string path, int options) rmdir( ) Yes
boolean dir_opendir(string path, int options) opendir( ) No
string dir_readdir(void) readdir( ) No
boolean dir_rewinddir(void) rewinddir( ) No
PHP 5
Method Function
only?
boolean dir_closedir(void) closedir( ) No
array url_stat(string path, int flags) stat( ) No

In many cases, a method will have no meaningful concept for a wrapper, or will be impossible to
implement due to technical limitations. For example, you cannot rename a file using HTTP.

Also, some higher-level functions, such as file_get_contents( ) and file_put_contents( ), call


multiple lower-level streams functions. For instance, you can't callfile_get_contents( ) if you
haven't implemented stream_open( ), stream_read( ), and stream_close( ).

If you try to call a method that's not defined by your wrapper, PHP issues a warning. Here's an
example using a contrived fake wrapper that has no methods:

file_get_contents('fake://not/a/real/wrapper');

PHP Warning: file_get_contents(fake://): failed to open stream:

"fake::stream_open" call failed

The wrapper method specifications are like an informal interface. The big difference is that you're not
required to implement all the methods.

This section shows how to implement a wrapper for PHP's shared memory functions. Shared memory
is a way to store data in your server's RAM. When it's stored in RAM, you can access the information
very quickly because you don't need to read from your disk drive.

Additionally, as the name suggests, this data is easily shared among different programs. This can be
multiple copies of PHP running on the same machine, such as in a typical web server setup. Or, it can
be other programs written in a different language.

The downside to using shared memory is that it takes up valuable RAM space, so you don't want to
store very large files in shared memory. Also, the data is lost completely when the machine is
rebooted. Finally, to use shared memory with Windows, you must run Windows 2000 or greater and
run PHP as module. It won't work with the CGI or CLI modules.

PHP has a couple of different ways to interact with shared memory. This wrapper uses theshmop
extension because it's most similar to the standard file interface and thus requires the least work to
fit it to the stream's interface.

The shmop extension is included in PHP, but it's not enabled by default. To turn it on, add the--
enable-shmop flag when compiling PHP.

8.4.1 Opening and Closing


A wrapper is a class with particular methods. Register a class as a wrapper using
stream_wrapper_register( ). This call registers the shmop protocol with the ShmopWrapper class:

stream_wrapper_register('shmop', 'ShmopWrapper');

The first argument is the name of the protocol and the second is the class name. If
stream_wrapper_register( ) cannot register the class, it returns false.

It's vital to implement stream_open( ) and stream_close( ) because you can't work with streams
without them-even file_get_contents( ) implicitly calls stream_open( ) and stream_close( )

The stream_open( ) method tries to open the file (or, in this example, the shared memory
segment). If it's successful, it returns true; it returns false if there's an error. The stream_close(
) method closes the file.

Example 8-2 shows how you implement stream_open( ) and stream_close( ) for ShmopWrapper.

Example 8-2. ShmopWrapper: stream_open( ) and stream_close( )

class ShmopWrapper {

private $id;

private $position;

private $length;

public function stream_open($path, $mode = "c", $options, &$opened_path) {

// hardcode some values for now

$project = 'p'; // project PHP

$perms = 0600; // user read and write only

$size = 16384;

list($scheme, $target) = explode('://', $path);

// convert path to key

if (-1 = = = ($key = ftok($target, $project))) {

if ($options & STREAM_REPORT_ERRORS) {


trigger_error("Cannot convert path to shmop key.",

E_USER_WARNING);

return false;

// mode fix ups for file_(get|put)_contents( )

if ($mode = = 'rb') {

$mode = 'c';

} elseif ($mode = = 'wb') {

$mode = 'w';

// illegal mode

if (strlen($mode) != 1 || stripos("acwn", $mode) = = = false) {

if ($options & STREAM_REPORT_ERRORS) {

trigger_error("Illegal mode. Must be one of: a, c, w, n.",

E_USER_WARNING);

return false;

if (! $this->id = shmop_open($key, $mode, $perms, $size)) {

if ($options & STREAM_REPORT_ERRORS) {

trigger_error("Cannot open shmop segment.", E_USER_WARNING);

}
return false;

$this->position = 0;

$this->length = shmop_size($this->id);

return true;

public function stream_close( ) {

shmop_close($this->id);

The stream_open( ) method takes four parameters: $path, $mode, $options, and &$opened_path.

The $path variable is the complete path passed to fopen( ), including the protocol-for example,
shmop://www/www.example.com/index.html. The $mode specifies how the stream is to be opened.
This is one of four potential values:

Access: open an existing segment for read-only access.

Create: create a new segment, or open an existing segment for read and write access.

Write: open an existing segment for read and write access, but do not create a new segment.

n
New: create a new segment, but fail if the segment already exists.

The third parameter is either or both of these two options:

STREAM_USE_PATH

Search the include_path when passed a relative path. If the file is found in the include_path
and opened, set the fourth parameter, &$opened_path, to its full pathname.

STREAM_REPORT_ERRORS

Report errors using trigger_error( ).

The stream_open( ) function in Example 8-2 first breaks apart the $path into the $scheme and the
$target. The $scheme should always be shmop, and the $target should be a full pathname to a file.

This pathname is then converted to a shmop key using ftok( ). The ftok( ) function converts a
filename and a project identifier into a unique key for shmop_open( ). This key distinguishes one
shared memory segment from the others. The project identifier must be a one-letter code. For now,
the project identifier is always p, for PHP.

When ftok( ) fails, it returns -1. When this occurs, stream_open( ) issues an error if the
STREAM_REPORT_ERRORS constant is set in $options. This condition is checked by logically ANDing the
two values together. The method then returns false and exits.

The next step is checking that the mode is a single digit long and contains one of the four acceptable
modes: a, c, n, or w. If this fails, a similar series of error handlers kicks in.

You're finally ready to open the shared memory segment by callingshmop_open( ). This function
takes four parameters. You've already seen the $key and the $mode, but it also uses the $perms and
$size arguments.

The $perms parameter is the set of file permissions to use when creating the segment, and the$size
parameter is the size of the segment to create in bytes. Like the project identifier, these two values
are currently fixed, but later on these will be editable using stream context options.

When everything goes okay, the shared memory identifier is stored in theid property and the
method returns true. If this fails, it returns false.

Thankfully, the stream_close( ) function is less complex. It releases the connection using
shmop_close( ) and doesn't return anything.

8.4.2 Reading and Writing


Now that you can open and close the stream, the next step is to enable reading and writing.Example
8-3 shows this.
Example 8-3. ShmopWrapper: stream_read( ) and stream_write( )

public function stream_read($count) {

// don't read beyond the end of the file

$count = min($count, $this->length - $this->position);

$data = shmop_read($this->id, $this->position, $count);

$this->position += strlen($data);

return $data;

public function stream_write($data) {

$bytes_written = shmop_write($this->id, $data, $this->position);

$this->position += $bytes_written;

return $bytes_written;

When you call fread( ) and fwrite( ), the streams-handling code invokes your wrapper's
stream_read( ) and stream_write( ) methods.

The fread( ) method takes one parameter, $count. This is the number of bytes to read from the
stream; however, you're responsible for checking that this number isn't greater than the remaining
bytes left in the stream. When $count is too big, the call to min( ) resets it to $this->length -
$this->position, which is the maximum number of remaining bytes.

You now read the data from the stream with shmop_read( ). This function takes the shared memory
handle, the position in the segment to read from, and the number of bytes to return. It returns the
data stored in that portion of the segment.

When you read data, you must update your position in the stream by the size of your read. This way,
you can read chunks of data from the stream and eventually reach the end. If you don't adjust the
position, you just read the same segment again and again.

To account for the read, increment $this->position by the string length of the data. Once that's
done, return the data.

Writing to shared memory is a similar process, but instead of taking the number of bytes to read and
returning data, it takes data and returns the number of bytes written. It also updates theposition
property.

You're almost done, but there are still three necessary methods remaining- stream_tell( ),
stream_eof( ), and stream_seek( )-which are contained in Example 8-4. They help the stream
know where it is, see if it's at the end of the file, and navigate through the file.

Example 8-4. ShmopWrapper: stream_tell( ), stream_eof( ),


stream_seek( )

public function stream_tell( ) {

return $this->position;

public function stream_eof( ) {

return $this->position >= $this->length;

public function stream_seek($offset, $whence) {

switch($whence) {

case SEEK_SET:

if ($offset >= 0 && $offset < $this->length) {

$this->position = $offset;

return true;

} else {

return false;

break;

case SEEK_CUR:

if ($offset >= 0 &&


$this->$position + $offset < $this->length) {

$this->position += $offset;

return true;

} else {

return false;

break;

case SEEK_END:

if ($this->length + $offset >= 0) {

$this->position = $this->length + $offset;

return true;

} else {

return false;

break;

default:

return false;

Because you're already tracking your position, the first method, stream_tell( ), is essentially an
accessor for the $position property.

The stream_eof( ) method reports when you're at the end of the segment. This occurs when the
current position is greater than or equal to the size of the shared memory segment. The position
should never exceed the $length, but it's better to be safe than sorry.

The last method is the most complex because it's really handling three different jobs. While the
primary goal of stream_seek( ) is moving to a new location in the stream, you can specify that
position in three different ways: SEEK_SET, SEEK_CUR, and SEEK_END. Here's how they're related:

SEEK_SET

Move $offset spaces from the beginning of the stream.

SEEK_CUR

Move $offset spaces from the current position of the stream.

SEEK_END

Move $offset spaces from the end of the stream. This value should be negative, so you move
back into the stream instead of past the end.

The stream_seek( ) method uses a switch statement because the three types are handled
similarly. After the request passes some out-of-bounds checking, to prevent moving past the
beginning or end of the segment, the position is updated and the method returnstrue. Bad seek
requests generate a return value of false.

8.4.3 Using ShmopWrapper


Now that you've implemented the basics necessary to get the wrapper up and running, use it:

stream_register_wrapper("shmop", "ShmopStream")

or die("Failed to register shmop wrapper");

$file = _ _FILE_ _; // pass current file to key generator

file_put_contents("shmop://$file", 'Rasmus Lerdorf');

print file_get_contents("shmop://$file");

Rasmus Lerdorf

This example saves the string Rasmus Lerdorf to a shared memory segment and then retrieves it
and prints it out. It uses the special _ _FILE_ _ constant as the pathname because it ensures the
path will always be valid.

Since this value (Rasmus Lerdorf) is stored in shared memory, it's available on subsequent
requests:
stream_register_wrapper("shmop", "ShmopWrapper")

or die("Failed to register shmop wrapper");

$file = _ _FILE_ _; // pass current file to key generator

print file_get_contents("shmop://$file");

Rasmus Lerdorf

This code wrote nothing to the segment: the value persisted from the previous example.

The shmop wrapper also works with the lower-level streams manipulation functions:

stream_register_wrapper("shmop", "ShmopWrapper")

or die("Failed to register shmop wrapper");

$file = _ _FILE_ _; // pass current file to key generator

$shmop = fopen("shmop://$file", 'c') or die("Can't open segment.");

fwrite($shmop, 'Zeev Suraski');

rewind($shmop);

print fread($shmop, 20);

fclose($shmop);

Zeev Suraskirf

Surprise! The old value of Rasmus Lerdorf was still stored in the segment, so calling fwrite( ) with
Zeev Suraski only overwrote the first 12 letters. This left the last two- rf-in the segment. That's
why you got the nonsense value of Zeev Suraskirf when you printed out the first 20 characters.

8.4.4 Deleting
A new feature in PHP 5 is the ability to delete files using streams. In PHP 4, you can only read and
write data, not delete it, so it was impossible to clean up existing resources.

True to PHP's Unix roots, the method to delete an item is calledunlink( ), as shown in Example 8-
5.

Example 8-5. ShmopWrapper: unlink( )

public function unlink($path) {

if ($this->stream_open($path, 'w', 0, $opened_path) &&

shmop_delete($this->id) &&

$this->stream_close( )) {

return true;

return false;

The unlink( ) method from Example 8-5 takes a path to delete, such as
shmop:///www/www.example.com/index.php. However, the shmop_delete( ) function needs a
shared memory resource.

To circumvent this problem, unlink( ) calls stream_open( ) to convert the path to a segment. Now
the id property holds the segment resource, which is passed to shmop_delete( ). Finally, to clean
up, the segment is closed.

Adding this method to your class lets you remove segments:

stream_register_wrapper("shmop", "ShmopWrapper")

or die("Failed to register shmop wrapper");

$file = _ _FILE_ _; // pass current file to key generator

unlink("shmop://$file");

$shmop = fopen("shmop://$file", 'c') or die("Can't open segment.");

fwrite($shmop, 'Zeev Suraski');

rewind($shmop);
print fread($shmop, 20);

fclose($shmop);

Zeev Suraski

Everything works okay now because the segment was cleared out before you wroteZeev Suraski.

8.4.5 Adding Context Options


There's still one problem with the wrapper. Back in stream_open( ), you hardcoded values for the
project name, the segment permissions, and the segment size. There isn't a good way to specify
alternative values using the regular wrapper://target syntax.

That's why streams implements stream contexts. You can use a context to modify the three fixed
values. First, here's how to pass context values to the wrapper:

$options = array(

'shmop' => array(

'size' => 32768

);

$context = stream_context_create($options);

file_put_contents("shmop://$file", $data, false, $context)

or die("Can't open segment.");

As with the http wrapper, place your options in a multidimensional array. The first array has a key of
shmop; the second array can have keys of project, perms, and size. This example sets the size
option to 32768 but leaves the other two options untouched.

After converting the array to a context with stream_context_create( ), pass it to


file_put_contents( ) as the third parameter.

Here's a revised start of stream_open( ) from Example 8-2 that processes the context options:

public function stream_open($path, $mode = "c", $options, &$opened_path) {

$contexts = stream_context_get_options($this->context);

$context = $contexts['shmop'];
$project = empty($context['project']) ? 'p' : $context['project'];

$perms = empty($context['perms']) ? 0600 : $context['perms'];

$size = empty($context['size']) ? 16384 : $context['size'];

// rest of method follows...

When a person passes a context, the wrapper object automatically stores it in the$context
property. Convert this context resource back into an array using stream_context_get_options( ).
The shmop options, if any, are stored in the shmop key of the array.

Once these options are stored in $context, you need to check whether each option is set. If it is, use
its values; otherwise, use the default value. This is done using the? : ternary operator.

The most important part of adding context options is documentation. There's no standard naming
convention, because options vary from wrapper to wrapper. So, without documentation, nobody will
know what options they can use. There's no documentation standard for wrappers, but
PhpDocumentor (http://pear.php.net/package/PhpDocumentor) is a good choice.

8.4.6 Other Methods


This shmop wrapper implements eight different wrapper methods:

stream_open( )

stream_close( )

stream_read( )

stream_write( )

stream_tell( )

stream_eof( )

stream_seek( )

unlink( )

However, that's less than half of the total number of available methods. You can also implement:
stream_flush( )

stream_stat( )

mkdir( )

rmdir( )

dir_opendir( )

dir_readdir

dir_rewinddir( )

dir_closedir

url_stat( )

Most of these methods operate on directories, but a few, such as stream_flush( ), work on files.

For more information on the other wrapper methods, consult Table 8-2 or read the documentation for
stream_wrapper_register( ) in the PHP Manual at http://www.php.net/stream-wrapper-register.

< Day Day Up >


< Day Day Up >

8.5 Filtering Streams


Use filters to modify the contents of a stream. They allow you to pre- and postprocess data without
altering the original. Stream filters are like output buffer handlers only better, because they're more
flexible and efficient.

Unlike the output buffer handler, which is best suited for information that's either being printed out or
stored in a variable, you can use a filter wherever you can use a stream. This makes it possible to
filter, for example, data going to a file. With an output buffer you'd need to store the entire dataset in
a buffer, capture it to a variable, and then send it to the file.

You can filter input data as well as output data. PHP's output buffering only works on output data. A
filter lets you automatically encode all HTML entities from a user's input, for example.

PHP comes with a few built-in filters, but you can also create your own. The built-in filters are:

string.rot13

Performs rot13 encoding

string.toupper

Turns all characters to uppercase

string.tolower

Turns all characters to lowercase

string.strip_tags

Eliminates HTML tags

convert.iconv.*

Alters the character set using iconv

Use stream_get_filters( ) to determine which filters are available. It works just like
stream_get_wrappers( ).
Filters observe an unofficial namespace convention. For instance, string filters begin withstring. and
conversion filters begin with convert..

To use a filter with a stream, you must associate the two usingstream_filter_prepend( ) or
stream_filter_append( ). Since you can place multiple filters upon a single stream, the first
function adds the new filter to the front of the list, whereas the second function adds it to the end. If
your stream uses only a single filter, there's no difference between the two functions.

8.5.1 Stripping HTML Tags


One of the built-in filters is string.strip_tags. This filter removes HTML tags from the data. For
example:

$html = 'I am <b>bold</b>. I am <i>italic</i>.';

$fp = fopen("php://output", "r");

stream_filter_prepend($fp, "string.strip_tags");

fwrite($fp, $html);

fclose($fp);

prints:

I am bold. I am italic.

This code opens a stream and then prepends the string.strip_tags filter to the filter chain. When
the string $html is sent to the stream using fwrite( ), the filter intercepts the stream and removes
the HTML tags.

8.5.2 Converting Character Sets


Another useful filter is convert.iconv.*. This allows you to convert text from one character set to
another.

As of PHP 5, the iconv library is enabled by default, but it's not required to run
PHP, so it's possible (but unlikely) that your copy of PHP won't have this filter.

For example, you have text stored in UTF-8 and want to convert it to ISO-8859-1 (aka Latin 1):
$fp = fopen("data.UTF-8.xml", "r");

stream_filter_prepend($fp, "convert.iconv.UTF-8/ISO-8859-1");

$converted = '';

while (!feof($fp)) {

$converted .= fread($fp, 1000);

fclose($fp);

This opens data.UTF-8.xml, a UTF-8-encoded XML file. The filter convert.iconv.UTF-8/ISO-8859-1


takes this UTF-8 data and converts it to ISO-8859-1.

convert.iconv.* is a special "wildcard" filter that matches any filter name that begins with
convert.iconv.. Specify the source and destination encodings after the period and separate them
with a slash (/). As long as iconv supports those encodings, the filter will work and there's no need
to explicitly write filters for every single combination of encoding translations.

You cannot get PHP to provide you with complete list of available encodings. Instead, use the
command-line iconv tool with the -l flag:

% iconv -l

This prints a long list of encodings, probably beginning with:

ANSI_X3.4-1968 ANSI_X3.4-1986 ASCII

UTF-8

ISO-10646-UCS-2 UCS-2 CSUNICODE

To determine if a system supports a particular encoding, check the return value oficonv( ):

if (!iconv('ASCII', 'UTF-8', '')) {

// Oops! iconv doesn't support this encoding

The function will return false when it cannot convert from one character set to another.

iconv supports two special modifiers for the destination encoding://TRANSLIT and //IGNORE. The
first option tells iconv that whenever it cannot exactly duplicate a character in the destination
encoding, it should try to approximate it using a series of other characters. The other option makes
iconv silently ignore any unconvertible characters.
For example, the file geb-UTF-8.txt holds the text Gödel, Escher, Bach. A straight conversion to
ASCII produces an error:

$fp = fopen("geb.UTF-8.txt", "r");

stream_filter_prepend($fp, "convert.iconv.UTF-8/ASCII");

$converted = '';

while (!feof($fp)) {

$converted .= fread($fp, 1000);

fclose($fp);

PHP Warning: fread( ): iconv stream filter ("UTF-8"=>"ASCII"): unknown error

Enabling the //IGNORE feature allows the conversion to occur:

$fp = fopen("geb.UTF-8.txt", "r");

stream_filter_prepend($fp, "convert.iconv.UTF-8/ASCII//IGNORE");

$converted = '';

while (!feof($fp)) {

$converted .= fread($fp, 1000);

fclose($fp);

However, the output isn't nice, because the ö is missing:

Gdel, Escher, Bach

The best solution is to use //TRANSLIT:

$fp = fopen("geb.UTF-8.txt", "r");

stream_filter_prepend($fp, "convert.iconv.UTF-8/ASCII//TRANSLIT");

$converted = '';

while (!feof($fp)) {
$converted .= fread($fp, 1000);

fclose($fp);

This produces a better-looking string:

G"odel, Escher, Bach

However, be careful when you use //TRANSLIT, as it can increase the number of characters. For
example, the single character ö becomes two characters: "o.

8.5.3 Using php://filter


Since it doesn't expose a file pointer such as fopen( ), the file_put_contents( ) function doesn't
give you the opportunity to attach a filter using stream_filter_append( ). The solution is to use
the php://filter target.

This weird target exists solely for the purpose of allowing you to integrate filters with functions such
as file_put_contents( ). It lets you specify both filters and another target, like so:

$html = 'I am <b>bold</b>. I am <i>italic</i>.';

// Stripping HTML tags on data sent to php://output

file_put_contents(

'php://filter/write=string.strip_tags/resource=php://output', $html);

I am bold. I am italic.

This code does the same thing as the longer version here:

// The long version

$fp = fopen("php://output", "r");

stream_filter_prepend($fp, "string.strip_tags");

fwrite($fp, $html);

fclose($fp);

Because filter isn't an actual target, you have to pass the true destination using the string
/resource=. For example, use /resource=php://output to send information to the current output
buffer.

You can also insert multiple filters into the stream. Filters can process data being read, written, or
both. (Some streams are unidirectional, but others are bidirectional.) To designate a filter for
reading, add /read=; for writing, add /write=; and, for both directions, add /=. For example,
/write=string.strip_tags strips HTML tags from any data being written.

Chain multiple filters with the pipe character (|). Filters are applied in the order present. So,
/write=string.strip_tags|string.string.toupper first strips tags and then uppercases letters.

< Day Day Up >


< Day Day Up >

8.6 Creating Filters


PHP 5 comes with very few stream filters, but it introduces the ability to write your own filters in PHP,
effectively giving you unlimited possibilities. PHP 4 requires all filters to be implemented in C.

The filter interface is much less complex than the wrapper interface. It has only three different
methods, and it's common to implement only a single method,filter( ). Table 8-3 contains an
overview of the API.

Table 8-3. Filter methods

Method Description
int filter(resource in, resource out, int Called during data filtering; may be called
&consumed, bool closing) multiple times per stream
void onCreate(void) Called during filter instantiation
void onClose(void) Called during filter destruction

The filter( ) method is where all the action takes place. Inside this method, you're required to
process the incoming data, filter it when you can, and then alert the filter of your progress.

This section shows how to implement two different filters: one that encodes special characters (such
as &amp; and &lt;) to their HTML entities equivalents, and one the does the reverse, by transforming
HTML entities back into characters.

8.6.1 Converting to HTML Entities


Example 8-6 shows a filter that encodes HTML entities using the htmlentities( ) function.

Example 8-6. Encoding HTML entities with a filter

class htmlentitiesFilter extends php_user_filter {

function filter($in, $out, &$consumed, $closing) {

while ($bucket = stream_bucket_make_writeable($in)) {


$bucket->data = htmlentities($bucket->data);

$consumed += $bucket->datalen;

stream_bucket_append($out, $bucket);

return PSFS_PASS_ON;

This class looks complicated because there are many new functions and objects, but it's actually quite
boring and simple.

Filters written in PHP are implemented as objects. There's a predefined base class,
php_user_filter, which is automatically available for you to use; it does not need to beincluded.
All filters must extend this class and implement thefilter( ) method.

The filter( ) method is where the actual data conversion takes place. This method takes four
parameters: $in, $out, &$consumed, and $closing. The first two parameters are the input and
output bucket resources. These resources hold stream data.

The third parameter, &$consumed, is set to the amount of data in the stream already processed, or
"eaten up," by the filter. This parameter must always be passed as a reference. The final argument is
$closing. It's set to true when filter( ) is called for the last time, so you can be sure to flush any
remaining data if necessary.

Your primary goal inside filter( ) is to take data from the input bucket, convert it, and then add it
to the output bucket. However, you can't just operate on the bucket resources directly; instead, you
need to call a few helper functions to convert the data from a resource to an object that is modifiable
in PHP.

The stream_bucket_make_writable( ) function retrieves a portion of the data from the input
bucket and converts it to a PHP bucket object. This object has two properties:data and datalen.
The data property is a string holding the bucket's data, whereas datalen is its length.

Since this filter wants to modify all the input data to escape HTML entities, pass$bucket->data to
htmlentities( ) and assign the return value back to $bucket->data. This alters the data inside the
bucket object.

Your next step is to let the filter know you've processed some data. Do this by incrementing the value
of $consumed by the length of the data you've filtered. Since in this case you've filtered the entire
data property, you can just add the value of $bucket->datalen.

The last step is to take the bucket object and add it as output. The stream_bucket_append( )
function takes a bucket resource and appends a bucket object to it. Therefore, to add the converted
bucket object back to the stream, call stream_bucket_append($out, $bucket).

This entire process takes place inside a while loop because the stream passes data to you in chunks
instead of sending the entire dataset at once. When there's no more data,
stream_bucket_make_writable( ) returns false and the loop terminates.

Once the loop is completed, you must return one of three constants: PSFS_PASS_ON, PSFS_FEED_ME,
or PSFS_ERR_FATAL. When everything goes as planned, return PSFS_PASS_ON. When everything's
okay but your filter cannot yet return any data because it needs additional data to complete the
filtering process, return PSFS_FEED_ME. Whenever there's an unrecoverable error, return
PSFS_ERR_FATAL.

Register the class as a filter with stream_filter_register( ):

stream_filter_register('convert.htmlentities', 'htmlentitiesFilter')

or die('Failed to register filter');

$html = 'I am <b>bold</b>. I am <i>italic</i>.';

$fp = fopen('php://output', 'r');

stream_filter_prepend($fp, 'convert.htmlentities');

fwrite($fp, $html);

fclose($fp);

This prints:

I am &lt;b&gt;bold&lt;/b&gt;. I am &lt;i&gt;italic&lt;/i&gt;.

8.6.2 Converting from HTML Entities


The inverse operation, decoding HTML entities, requires special handling. Because HTML entities are
multiple characters, they may span buckets. For example, one bucket could end with&a and the next
one could begin with mp;.

When you don't have a good algorithm for identifying partial sequences, store the entire dataset into
a property and wait until the $closing parameter is true, as in Example 8-7.

Example 8-7. Decoding HTML entities with a filter

class dehtmlentitiesFilter extends php_user_filter {


function onCreate( ) {

$this->data = '';

return true;

function filter($in, $out, &$consumed, $closing) {

while ($bucket = stream_bucket_make_writeable($in)) {

$this->data .= $bucket->data;

$this->bucket = $bucket;

$consumed = 0;

if ($closing) {

$consumed += strlen($this->data);

// decode named entities

$this->data = html_entity_decode($this->data);

$this->bucket->data = $this->data;

$this->bucket->datalen = strlen($this->data);

stream_bucket_append($out, $this->bucket);

return PSFS_PASS_ON;

return PSFS_FEED_ME;
}

Unlike the filter in Example 8-6, Example 8-7 uses the onCreate( ) method to initialize a property.
This method is run the first time the filter is invoked. There's also anonClose( ) method that's
called when the filter finishes, but neither of the two example filters uses it.

Instead of reading in buckets and converting data as it arrives, filter( ) stores all the data locally
and executes the conversion only at the end. This way you're assured that you're never caught mid-
entity.

Inside the while, append each bucket's data to the $data property. You also store the bucket in the
$bucket property; otherwise, PHP destroys the bucket when the loop terminates. Finally, since none
of the data is consumed right now, always set $consumed to 0.

After processing all the buckets, check to see if the stream is closing. If it'strue, you know all the
data has arrived, so you can begin the conversion.

First, update $consumed to the length of the data. Then, decode the HTML entities with
html_entity_decode( ), storing the results in $this->data.

With the mapping complete, it's time to update the bucket. Its data property gets the converted
data, and its datalen property is set to the data's length. This is not the same as $consumed. It will
be shorter because you're converting &amp; back into &, for example.

Now append the bucket to the output and return PSFS_PASS_ON. However, when $closing is false,
omit the conversions and return PSFS_FEED_ME to let the stream know that your filter needs
additional data to process.

Use this filter like the HTML entity encoding filter:

stream_filter_register("convert.dehtmlentities", "dehtmlentitiesFilter")

or die("Failed to register filter");

$ascii = 'I am &lt;b&gt;bold&lt;/b&gt;. I am &lt;i&gt;italic&lt;/i&gt;.';

$fp = fopen("php://output", "r");

stream_filter_prepend($fp, "convert.dehtmlentities");

fwrite($fp, $html);

fclose($fp);

I am <b>bold</b>. I am <i>italic</i>.
< Day Day Up >
< Day Day Up >

Chapter 9. Other Extensions


This chapter covers three new parts of PHP 5: the SOAP and Tidy extensions and the Reflection
classes.

SOAP is an XML-based protocol for exchanging information over the Internet that you can use to
build or utilize web services. The SOAP extension provides you with an easy way to both query SOAP
servers and implement a SOAP server of your own.

The Tidy extension tackles the problem of invalid HTML. It converts malformed web pages into fully
valid HTML and XHTML. Tidy also lets you optimize your web pages by removing unnecessary
characters. This translates into faster download times and lower bandwidth bills, a double advantage.
Finally, Tidy helps you out when you're in the unfortunate position of parsing HTML to extract data,
such as when you're screen-scraping.

With the Reflection classes, you can programmatically extract information about classes, functions,
methods, and properties. This allows you to produce automated class documentation, write a PHP
debugger, and other tasks that require object introspection.

< Day Day Up >


< Day Day Up >

9.1 SOAP
Web services allow you to exchange information over HTTP using XML. When you want to find out the
weather forecast for New York City, the current stock price of IBM, or the best-selling DVD according
to Amazon.com, you can write a short script to gather that data in a format you can easily
manipulate. From a developer's perspective, it's as if you're calling a local function that returns a
value.

A major advantage of web services is ubiquity across platforms and languages. A PHP script running
on Linux can talk to an IIS server on a Windows box using ASP without any communication problems.
When the server switches over to Solaris, Apache, and JSP, everything transitions without a glitch.

SOAP is a popular web services format. It's a W3C standard for passing messages across the network
and calling functions on remote computers. SOAP provides developers with many options; however,
this flexibility comes at a price. SOAP is quite complex, and the full specification is large and growing.

As a result, PHP 4's SOAP support is only fair. While there are a few SOAP packages, the most
mature ones are written in PHP instead of C. Therefore, they are slow, and you have to download and
install them yourself.

With PHP 5, there's finally a usable SOAP extension written in C. Additionally, it's bundled with PHP 5,
so you can enable SOAP support by adding --enable-soap to your PHP configure line. The only
external library you need is libxml2, which is the same requirement for any of PHP 5's XML
extensions.

Right now, this extension implements most, but not all, of SOAP 1.2. This is a significant
improvement over previous C extension, and Zend's Dmitry Stogov is actively working on filling in the
remaining pieces. Complete details on SOAP are available on the W3 web site at
http://www.w3.org/2000/xp/Group/ and in Programming Web Services with SOAP, by James Snell,
Doug Tidwell, and Pavel Kulchenko (O'Reilly).

This section shows how to create a SOAP client and a SOAP server. The SOAP client demonstrates
how to query a server to find out the current temperature. Then, the Section 9.1.5 shows how to
replicate a similar interface using PHP. Along the way, you also see how to handle SOAP faults, either
with exceptions or with traditional error-handling code.

9.1.1 WSDL
Before getting into the details of SOAP, it's necessary to first discussWSDL. WSDL (Web Services
Description Language) is an XML vocabulary that lets the implementor create a file that defines what
methods and arguments his web service supports. This file is then placed on the Web for others to
read.

Since WSDL is written in XML, it's not particularly friendly for humans, but it's great for machines.
When you point the SOAP extension at a WSDL file, the extension automatically creates an object for
the web service, and you can manipulate this object as you would a PHP class.

The object even knows what parameters each method takes and each parameter's type. This is
important because, unlike PHP, SOAP is a strictly typed language. You cannot provide a string1 when
SOAP wants an integer 1. WSDL allows the SOAP extension to coerce PHP variables into the
appropriate types without any action on your part.

Unfortunately, the SOAP extension cannot generate WSDL for your own web service servers. This
makes it difficult, although not impossible, for people to talk to your web services, because they need
to go through more steps to make a client request. Hopefully, this feature will be added in the near
future. This is addressed in more detail in Section 9.1.5.

For more on WSDL, see http://www.w3.org/TR/wsdl, or read Chapter 6 of Ethan Cerami's Web
Services Essentials, available at http://www.oreilly.com/catalog/webservess/chapter/ch06.html.

9.1.2 Requesting Information with a SOAP Client


The most common task when using a web service is querying a server for information. Before you
can do this, of course, you need to know the location of a web service to query and what parameters
the method takes.

One place to go is XMethods (http://www.xmethods.net), a site that provides a few useful web
services and also contains a giant directory of publicly available web services. The examples in this
section query XMethods's Temperature service, which lets you discover the current temperature for a
Zip Code.

9.1.2.1 WSDL requests

Whenever possible, you want to know the location of the server's WSDL file. This makes it much
easier to make SOAP requests. Example 9-1 shows how to make a query using WSDL.

Example 9-1. SOAP client using WSDL

$wsdl_url =

'http://www.xmethods.net/sd/2001/TemperatureService.wsdl';

$client = new SoapClient($wsdl_url);

$temp = $client->getTemp('10001'); // New York, NY

print $temp;

68
From XMethods's web site, you know that the WSDL file for this service is at
http://www.xmethods.net/sd/2001/TemperatureService.wsdl.

You now instantiate a new SoapClient object by passing $wsdl_url, the location of the WSDL file, to
the constructor. This returns a client object, $client, that you use to make SOAP requests.

The constructor creates a SOAP client, but you still need to make the actual query itself. The method
to find the temperature is called getTemp( ). It takes one argument, the Zip Code. Pass your Zip
Code, in this case 10001, directly to the method.

When you call $client->getTemp(10001), the SOAP extension converts the PHP string 10001 to a
SOAP message written in XML and sends an HTTP request to XMethods's server. After XMethods
receives and processes your query, it replies with a SOAP message of its own. The SOAP extension
listens for this response and parses the XML into a PHP object, which is then returned by the method
and stored in $temp.

The $temp variable now holds the current temperature, in Fahrenheit. Right now it's 68 degrees in
New York City.

9.1.2.2 Non-WSDL requests

It's harder to query web services that don't provide a WSDL file. Example 9-2 shows how to query
the identical service without WSDL.

Example 9-2. SOAP client without WSDL

$opts = array('location' =>

'http://services.xmethods.net:80/soap/servlet/rpcrouter',

'uri' => 'urn:xmethods-Temperature');

$client = new SoapClient(NULL, $opts);

$temp = $client->_ _call('getTemp', array('10001'));

print $temp;

68

In Example 9-2, since you're not using WSDL, pass NULL as the first argument to SoapClient. This
tells the SOAP extension that you're passing the details about the web service in the second
parameter.

This information is stored as array. At a minimum, you must provide two entries: the URL where the
SOAP server is located and the namespace URI that identifies the service.

The server's URL is the location element; here, the server is at


http://services.xmethods.net:80/soap/servlet/rpcrouter. The server's namespace is set using the uri
element. This is urn:xmethods-Temperature.

Now you have a SOAP client, but with a non-WSDL-based client, you can't directly invoke SOAP
methods on the $client object. Instead, you reference the _ _call( ) method, passing the method
name as your first argument and an array of parameters as the second.

Since the SOAP client no longer knows how many parameters to expect, you must bundle your
parameters to _ _call( ) inside of an array. Therefore, your Zip Code is now passed as
array('10001') instead of '10001'.

This code is more complex than the WSDL solution, and it even takes advantage of some default
SOAP settings assumed by SoapClient.

9.1.2.3 Multiple parameter queries

The XMethods getTemp( ) method takes one only parameter, the Zip Code. This allows you to pass it
directly to the method because there's no potential confusion about which value belongs to which
parameter.

Some web service methods take multiple parameters. For these methods, you can either remember
the argument order or pass the arguments in an array. The parameter names and values are the
array element keys and values, respectively.

Example 9-3 demonstrates what you would do if the getTemp( ) method allowed you to specify a Zip
Code and temperature scale.

Example 9-3. SOAP client with multiple parameters

$wsdl_url =

'http://www.example.com/TemperatureService.wsdl';

$client = new SoapClient($wsdl_url);

$params = array(

'Zipcode' => 10001, // New York, NY

'scale' => 'C' // Celsius


);

$temp = $client->getTemp($params);

20

The Zipcode parameter is the five-digit Zip Code, and scale is either F or C, for Fahrenheit and
Celsius, respectively.

This code won't actually work, because XMethods doesn't support multiple conversion scales.
However, the later Section 9.1.5 demonstrates how to implement this example as a SOAP web
service.

9.1.3 Catching SOAP Faults


When a SOAP server generates an error, it returns a SOAP fault. This can be a mistake on your part,
such as calling a method that doesn't exist or passing the incorrect number (or type) of parameters,
or it can be a server error. For instance, the service lacks temperature information for a particular Zip
Code, but for reasons external to your SOAP request.

The SOAP extension transforms SOAP faults into PHP exceptions, as shown inExample 9-4.

Example 9-4. Detecting SOAP faults with exceptions

try {

$wsdl_url =

'http://www.example.com/TemperatureService.wsdl';

$client = new SoapClient($wsdl_url);

$temp = $client->getTemp('New York'); // This should be a Zip Code

print $temp;

} (SOAPFault $exception) {

print $exception;

SoapFault exception: [SOAP-ENV:Server] Zip Code New York is unknown.


in /www/www.example.com/soap.php:8

Stack trace:

#0 /www/www.example.com/soap.php(8): SoapClient->getTemp('getTemp', Array)

#1 {main}

Since the server requires a Zip Code but Example 9-4 passed New York, the server returned a SOAP
fault. Printing the exception gives you, among other debugging information, the errorZip Code New
York is unknown..

If you dislike exceptions, you can make SOAP return faults instead by setting theexceptions
configuration setting to 0. This is done in Example 9-5.

Example 9-5. Detecting SOAP faults without exceptions

$wsdl_url =

'http://www.example.com/TemperatureService.wsdl';

// Disable exceptions

$opts = array('exceptions' => 0);

$client = new SoapClient($wsdl_url, $opts);

$temp = $client->getTemp('New York'); // This should be a Zip Code

if (is_soap_fault($temp)) {

print $exception;

} else {

print $temp;

SoapFault exception: [SOAP-ENV:Server] Zip Code New York is unknown.

in /www/www.example.com/soap.php:8

#0 {main}
To alter the default settings for a SoapClient object, pass in an array as the second argument to the
constructor. This is the same array that you use to specify information about non-WSDL servers.

When exceptions are disabled, $temp contains either the valid response or a SOAP fault. Check
is_soap_fault( ) to discover if there's an error.

9.1.4 Caching WSDL


The largest problem with WSDL is that before you can make the actual request, the SOAP extension
needs to fetch the WSDL file over the Internet and parse the document to create the proxy object.
This effectively doubles the time required to make the query.

The SOAP extension solves this by implementing a caching system. When a local WSDL file exists, the
extension uses the local copy instead of making another request.

The caching system is controlled by three configuration directives:

soap.wsdl_cache_enabled

Whether to enable the WSDL cache

soap.wsdl_cache_dir

Where to store the WSDL files

soap.wsdl_cache_ttl

How long to keep the WSDL files

By default, all WSDL files are cached in the /tmp directory for 86000 seconds, or one day.

Adjust the soap.wsdl_cache_ttl value to a higher number if you know the WSDL file is unlikely to
change (for instance, you're in control of the SOAP server or you know you'll receive advance notice
of any modifications). Turn off soap.wsdl_cache_enabled when you're developing a web service and
you know you're going to be modifying a WSDL file on a frequent basis.

9.1.5 Processing Requests with a SOAP Server


To process SOAP requests, you must first create a class to contain the methods you will allow people
to query using SOAP. Then, create a SOAPServer instance to handle the job of handing off the SOAP
requests to your class's methods.
9.1.5.1 Implementing getTemp( ) in PHP

Example 9-6 is a replication of the XMethods temperature SOAP server in PHP using the SOAP
extension.

Example 9-6. Implementing a SOAP server

class TemperatureService {

private $temps = array('10001' => 68);

public function getTemp($Zipcode) {

if (isset($this->temps[$Zipcode])) {

return $this->temps[$Zipcode];

} else {

throw new SoapFault('Server', "Zip Code $Zipcode is unknown.");

$server = new SoapServer(NULL,

array('uri' => 'http://www.example.org/temp'));

$server->setClass('TemperatureService');

$server->handle( );

In Example 9-6, the TemperatureService class contains getTemp( ), the one SOAP method
supported by your web service.

The getTemp( ) method takes a Zip Code and returns the current temperature. Since you don't have
access to actual temperature data, getTemp( ) checks the private $temp property. That property
holds a fixed array that maps Zip Codes to the "current" temperature.

When the Zip Code isn't in the array, the SOAP server issues a SoapFault. This fault is thrown using
PHP's exception-handling mechanism, and the SOAP extension will automatically convert the
exception into the correct XML to signal an error in SOAP.
Once your class is complete, there are three commands to create, configure, and start the SOAP
server. The first step is to instantiate a new instance of SoapServer. The first parameter is a WSDL
file that describes the server. Since you don't have one, passNULL instead, and add the information
using the second parameter.

The second argument takes an array of items. In this example, there's only a uri element (which is
set to http://www.example.com/temp). Each SOAP server is uniquely identified by a namespace of
your own choosing, and this one lives in http://www.example.com/temp.

Now you bind the object to the server by calling setClass( ). This tells the SOAP server to call the
methods of the TemperatureService class when processing requests.

The last step is to make your PHP script grab the incoming SOAP request and give it to the SOAP
server for processing. This is done with a call to handle( ).

This is not complicated. However, there's one problem: since the SOAP extension can't (yet)
generate WSDL for a PHP class, it's not easy for people to query your SOAP server. They can't just
read in the WSDL and call a method. Instead, they need to use the non-WSDL query technique.

Example 9-7 shows what you need to do instead to query your web service.

Example 9-7. Querying your SOAP server

try {

$opts = array('location' => 'http://www.example.org/temp.php',

'uri' => 'http://www.example.org/temp');

$client = new SoapClient(NULL, $opts);

$temp = $client->_ _call('getTemp', array('10001'));

print $temp;

} catch (SOAPFault $e) {

print $e;

This is similar to Example 9-2, but there's a different location and uri. The location is
http://www.example.org/temp.php, or whatever URL you choose to place your script. The uri
namespace needs to match the uri element you passed to SoapServer, so it's
http://www.example.org/temp.

One short-term solution to the lack of WSDL generation capabilities is to use thePEAR SOAP classes
as the backend to your SOAP server. PEAR::SOAP can autogenerate WSDL, so that solves the
problem. However, PEAR::SOAP is slower than the SOAP extension because it is written in PHP
instead of C.
If you need the speed of the SOAP extension, you can first hook up your class to PEAR::SOAP, have
it generate your WSDL, and then hook up your class to the SOAP extension. This isn't as complex as
it sounds, but it's not explained here, because hopefully this workaround won't be necessary for very
long.

For more on PEAR::SOAP, including how to make it create a WSDL file for your server, seeEssential
PHP Tools by David Sklar (Apress).

9.1.5.2 Optional parameters

You can modify getTemp( ) to support an optional temperature scale parameter that controls
whether the temperature is reported in Fahrenheit or Celsius, as shown inExample 9-8.

Example 9-8. Extending your SOAP server

class WeatherSOAP {

private $temps = array(10001 => 68);

function getTemp($Zipcode, $scale = 'F') {

if (isset($this->temps[$Zipcode])) {

if ($scale = = 'F') {

return $this->temps[$Zipcode];

} elseif ($scale = = 'C') {

// Convert from F to C

return ($this->temps[$Zipcode] - 32) / 9 * 5;

} else {

throw new SoapFault('Server', "Invalid temperature scale.");

} else {

throw new SoapFault('Server', "Zip Code $Zipcode is unknown.");

}
}

In Example 9-8, when $scale is set to C, the server converts the temperature to Celsius before it's
returned.

To trigger the conversion, add another element to the argument array, as in Example 9-9.

Example 9-9. Finding the temperature in Celsius

try {

$opts = array('location' => 'http://www.example.org/temp.php',

'uri' => 'http://www.example.org/temp');

$client = new SoapClient(NULL, $opts);

$words = $client->_ _call('getTemp',

array('10001', 'C'),

array('uri' => 'http://www.example.org/temp'));

} catch (SOAPFault $e) {

print $e;

When your server uses WSDL, you can define your method parameters in any order using an array,
like this:

$params = array('scale' => 'C', 'Zipcode' => '10001')

However, when you're not using WSDL, as in this case, you must define them in the order that the
method expects. WSDL lets the server rearrange the parameters into the correct order, something
the server cannot do otherwise.

< Day Day Up >


< Day Day Up >

9.2 Tidy
The Tidy extension "cleans up" messy HTML and XML files into valid and pretty-looking documents.
This feature is particularly useful when you're serving lots of externally generated content.

For example, you want to allow visitors to enter HTML-enabled messages, but you don't want them
to be able to create an invalid page. Manually checking each post is quite laborious, but with Tidy you
can automate this process.

Alternatively, Tidy can be used to reformat documents, either to reduce their file size or to make
them easily understandable by humans. The first option saves you bandwidth, making your pages
arrive more quickly and reducing your overall hosting costs. The second option simplifies your
debugging process, as you're not tracking down stray closing tags.

The Tidy extension is bundled with PHP, but not enabled, because it requires you to install the Tidy
library. Download the Tidy library from http://tidy.sourceforge.net/ and add --with-tidy=DIR to
turn on Tidy support in PHP.

9.2.1 Basics
Interacting with Tidy is a simple three step process. You parse the file, then clean its contents, and
finally print or save the repaired file.

Use tidy_parse_file( ) to read in a file for tidying:

$tidy = tidy_parse_file('index.html');

When your data is in a string, use tidy_parse_string( ) instead:

// This string is missing a closing </i> tag

$tidy = tidy_parse_string('I am <b>bold and I am <i>bold and italic</b>');

Transform the document using the tidy_clean_repair( ) command:

$tidy = tidy_parse_string('I am <b>bold and I am <i>bold and italic</b>');

tidy_clean_repair($tidy);

The tidy_clean_repair( ) function takes a Tidy resource. It returns true if everything went okay,
and false on an error. It does not return the tidied document. Use tidy_get_output( ) to retrieve
the altered file:
$tidy = tidy_parse_string('I am <b>bold and I am <i>bold and italic</b>');

tidy_clean_repair($tidy);

print tidy_get_output($tidy);

This prints:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">

<html>

<head>

<title></title>

</head>

<body>

I am <b>bold and I am <i>bold and italic</i></b>

</body>

</html>

Tidy has not only repaired the missing </i> tag, but also turned the string into a valid HTML 3.2 file.

9.2.2 Configuring Tidy


You can configure Tidy in innumerable ways. These options can be set at parse time in an array or in
a configuration file.

For example, to make Tidy return only the body of a cleaned document:

$options = array('show-body-only' => true);

$tidy = tidy_parse_string('I am <b>bold and I am <i>bold and italic</b>');

tidy_clean_repair($tidy);

print tidy_get_output($tidy);

I am <b>bold and I am <i>bold and italic</i></b>

This is useful when you're cleaning document fragments, such as message board posts or HTML that
is placed inside a template.
Alternatively, you can place this information in a file and pass the filename:

show-body-only: true

logical-emphasis: true

In the configuration file, place each individual option on a new line, and separate options with acolon
(:). Then, provide Tidy with its location:

$tidy = tidy_parse_string('I am <b>bold and I am <i>bold and italic</b>',

'tidy.cnf');

tidy_clean_repair($tidy);

print tidy_get_output($tidy);

I am <strong>bold and I am <em>bold and italic</em></strong>

In addition to fixing the HTML, turning on logical-emphasis has switched your b and i tags to their
logical equivalents.

Table 9-1 contains commonly used Tidy settings. A complete list of available options isat
http://tidy.sourceforge.net/docs/quickref.html.

Table 9-1. Important Tidy configuration options

Name Description Values Default


clean Should Tidy convert font tags to CSS? Boolean no

hide-endtags Omit ending tags? Boolean no


indent Indent block-level tags? yes, no, auto no
indent-spaces Number of spaces per indent Integer 2

markup Create a "Pretty Print" version of the file? Boolean yes


output-xml Output XML instead of HTML? Boolean no

output-xhtml Output XHTML instead of HTML? Boolean no


show-body-only Only return document body Boolean no

wrap Length of line wrap Integer 66


wrap-attributes Wrap attribute values? Boolean no
wrap-php Wrap PHP code? Boolean no
9.2.3 Optimize Files
Tidy provides options that can reduce your file size by stripping away extra whitespace and
comments and by converting verbose <font> tags into CSS.

Here is a sample configuration for Tidy that aggressively strips away as many unneeded characters
as possible:

$options = array(

'clean' => true,

'drop-proprietary-attributes' => true,

'drop-empty-paras' => true,

'hide-comments' => true,

'hide-endtags' => true,

'join-classes' => true,

'join-styles' => true,

'wrap' => 0,

);

$tidy = tidy_parse_file('http://www.example.org/', $options);

tidy_clean_repair($tidy);

print $tidy;

The overall effect of these options is to eliminate HTML that the browser doesn't use when rendering
the page and to combine duplicated styles into one unified style. Each setting is detailed inTable 9-2.

Remember that even a small improvement in page size is multiplied by every single page your server
delivers. The combined reduction in bandwidth may translate to serious cost savings on a high-traffic
site.

Table 9-2. Optimizing Tidy configuration options


Name Description Values Default
Convert presentational tags, such as <center>, with
clean Boolean no
style rules?
drop-proprietary- Eliminate proprietary attributes added by programs
Boolean no
attributes such as Microsoft Office?
Eliminate <font> tags when used with the clean
drop-font-tags Boolean no
option?
drop-empty-paras Eliminate empty <p> tags? Boolean yes
hide-comments Remove HTML comments? Boolean no

Remove closing tags when possible according to the


hide-endtags Boolean no
Document Type?
join-classes Merge related classes together? Boolean no
join-styles Merge related styles together? Boolean yes
Width for line wrapping (a value of 0 disables
wrap Integer 68
wrapping).

9.2.4 Object-Oriented Interface


Tidy also has an object-oriented interface:

$tidy = new Tidy('I am <b>bold and I am <i>bold and italic</b>');

$tidy->cleanRepair( );

print $this->getOutput( );

Like other extensions with dual procedural and OO interfaces, Tidy's methods use studlyCaps instead
of underscores. Additionally, you don't pass a Tidy resource to the methods, because the resource is
stored in the object.

< Day Day Up >


< Day Day Up >

9.3 Reflection
The Reflection classes allow you to retrieve detailed data about classes. Although you don't need to
use them in most web applications, they're invaluable when you're writing a program such as a class
documenter, unit tester, or debugger. These applications all require generic class-manipulation
routines.

PHP 4 lets you discover information about classes using a series of functions. There's
get_class_methods( ) to get an array of class methods and get_class_vars( ) for an array of
properties. These functions aren't integrated, so you cannot find out all the information about a class
at once.

The functions are also very simplistic. They don't contain any of the new information that you can set
in PHP 5. For example, there's no way to distinguish between private, protected, and public
methods and properties.

That's where the Reflection classes come in. They help you extract from a class or method any piece
of data you need.

9.3.1 Getting an Overview with Reflection::export


To understand how the Reflection classes work, Example 9-10 contains an example Person class that
uses many of PHP 5's OO features.

Example 9-10. Person class

class Person {

public $name;

protected $spouse;

private $password;

public function _ _construct($name) {

$this->name = $name

}
public function getName( ) {

return $name;

protected function setSpouse(Person $spouse) {

if (!isset($this->spouse)) {

$this->spouse = $spouse;

private function setPassword($password) {

$this->password = $password;

For a quick overview of the class, call Reflection::export( ):

Reflection::export(new ReflectionClass('Person'));

Class [ <user> class Person ] {

@@ /www/reflection.php 3-25

- Constants [0] {

- Static properties [0] {

- Static methods [0] {


}

- Properties [3] {

Property [ <default> public $name ]

Property [ <default> protected $spouse ]

Property [ <default> private $password ]

- Methods [4] {

Method [ <user> <ctor> public method _ _construct ] {

@@ /www/reflection.php 8 - 10

- Parameters [1] {

Parameter #0 [ $name ]

Method [ <user> public method getName ] {

@@ /www/reflection.php 12 - 14

Method [ <user> protected method setSpouse ] {

@@ /www/reflection.php 16 - 20

- Parameters [1] {

Parameter #0 [ Person or NULL $spouse ]


}

Method [ <user> private method setPassword ] {

@@ /www/reflection.php 22 - 24

- Parameters [1] {

Parameter #0 [ $password ]

The Reflection::export( ) static method takes an instance of the ReflectionClass class and
returns a copious amount of information. As you can see, it details the number of constants, static
properties, static methods, properties, and methods in the class. Each item is broken down into
component parts. For instance, all the entries contain visibility identifiers (private, protected, or
public), and methods have a list of their parameters underneath their definition.

Reflection::export( ) not only reports the file where everything is defined, but even gives the line
numbers! This lets you extract code from a file and place it in your documentation.

9.3.2 Inspecting Classes, Methods, and Properties


Reflection::export( ) is a handy way to see everything about a class at once, but it's not a very
useful method of extracting specific pieces of information.

That's where the individual Reflection classes come in. There's a Reflection class for every major
entity in PHP: classes, functions, methods, parameters, properties, and even extensions. Instances of
these classes have methods that you can query to determine whether a class isabstract, a method
is final, or a property is private. For example:

$class = new ReflectionClass('Person');

if ($class->isAbstract( )) {

// Person is abstract
}

$method = new ReflectionMethod('Person', 'getName');

if ($method->isFinal( )) {

// Person::getName( ) is final

$property = new ReflectionProperty('Person', 'name');

if ($property->isPrivate( )) {

// Person::name is private

To inspect a class, create a new ReflectionClass, passing the class name to the constructor. You
can query the returned object using any of the methods listed in the next section inTable 9-3.

The ReflectionMethod and ReflectionProperty classes operate similarly, except that they take
two arguments. The first argument is still the class name, and the second is the method or property
name. Table 9-5 has a list of all the ReflectionMethod methods, and Table 9-6 covers
ReflectionProperty.

While it's somewhat useful to check specific methods or properties, the utility of the Reflection classes
shines through when they're used in conjunction with each other. For instance, you want to create
API documentation for your classes. However, you only want to document public methods because
private and protected methods are reserved for internal use. Example 9-11 shows how to do this,
using the Person class in Example 9-10 as a base.

Example 9-11. Printing public methods

$class = new ReflectionClass('Person');

foreach ($class->getMethods( ) as $method) {

if ($method->isPublic( )) {

print $method->getName( ) . "( )\n";

}
_ _construct( )

getName( )

The getMethods( ) method in Example 9-11 returns an array containing all the methods in the class.
The elements of the array are ReflectionMethod( ) objects.

A ReflectionMethod object can be intraspected similarly to a ReflectionClass. Therefore, you can


call isPublic( ) to discover whether a method is declared public. If it is, the example uses
getName( ) to retrieve its name and prints it out.

You can use another class, ReflectionParameter, to add further detail to your documentation, as in
Example 9-12.

Example 9-12. Printing public methods with parameters

$class = new ReflectionClass('Person');

foreach ($class->getMethods( ) as $method) {

if ($method->isPublic( )) {

print $method->getName( ) . '(';

$params = '';

foreach($method->getParameters( ) as $parameter) {

$params .= '$' . $parameter->getName( ) . ', ';

print substr($params, 0, -2) . ")\n";

Now you can also list method parameters:

_ _construct($name)

getName( )

The ReflectionParameter methods appear in Table 9-7.


9.3.3 Reflection Class Methods
This section provides a complete listing of every method in all six of the Reflection classes. Each entry
in Tables Table 9-3 through Table 9-8 details what parameters a method takes and what types of
values it returns.

There are just over 100 methods in total, but the number of unique methods is almost half of that. As
you can see, there's a lot of duplication. For example, classes, methods, and properties can all be
declared public; therefore, the ReflectionClass, ReflectionMethod, and ReflectionProperty
classes all have an isPublic( ) method.

The method names are largely self-explanatory, but here's a quick overview that explains the naming
and behavior patterns.

All of the methods beginning with get retrieve values or objects. For instance, getName( ) returns
the object's name.

Some get methods return an array of objects, such as getMethods( ), which returns an array of
ReflectionMethods. These methods are marked with square array brackets ([ ]), like
ReflectionMethod[ ].

If a method begins with is, it returns a boolean value: either true or false.

One method different from the rest is invoke( ). This method is similar to call_user_func( ), as it
lets you pass arguments to a method or function. For example:

function add($i, $j) {

return $i + $j;

$math = new ReflectionFunction('add');

print $math->invoke(1, 2);

While it makes little sense to create a ReflectionFunction just to call invoke( ), it's handy when
you're using the Reflection classes to automate the processing of class methods. One example
program is a unit testing application that takes an object and runs all methods beginning with the
word test.

Table 9-3. ReflectionClass methods


Name Returns
ReflectionClass::_ _construct(mixed argument) throws
ReflectionClass
ReflectionException
ReflectionClass::_ _toString( ) string

ReflectionClass::export(mixed argument, [, bool return])


mixed
throws ReflectionException
ReflectionClass::getConstant(string name) mixed

ReflectionClass::getConstants( ) array
ReflectionClass::getConstructor( ) ReflectionMethod

ReflectionClass ReflectionProperty::getDeclaringClass( ) ReflectionClass

ReflectionClass::getDefaultProperties( ) array

ReflectionClass::getDocComment( ) string

ReflectionClass::getEndLine( ) int
ReflectionExtension or
ReflectionClass::getExtension( )
NULL

ReflectionClass::getExtensionName( ) string or false


ReflectionClass::getFileName( ) string

ReflectionClass::getInterfaces( ) ReflectionClass[ ]

ReflectionClass::getMethod(string name) ReflectionMethod


ReflectionClass::getMethods( ) ReflectionMethod[ ]

ReflectionClass::getModifiers( ) int
ReflectionClass::getName( ) string

ReflectionClass::getParentClass( ) ReflectionClass
ReflectionClass::getProperties( ) ReflectionProperty[ ]

ReflectionClass::getProperty(string name) ReflectionProperty


ReflectionClass::getStartLine( ) int
ReflectionClass::getStaticProperties( ) array

ReflectionClass::implementsInterface(string or
boolean
ReflectionClass interface_name)

ReflectionClass::isAbstract( ) boolean
ReflectionClass::isFinal( ) boolean

ReflectionClass::isInstance(object) boolean
ReflectionClass::isInstantiable( ) boolean
Name Returns
ReflectionClass::isInterface( ) boolean

ReflectionClass::isInternal( ) boolean

ReflectionClass::isIterateable( ) boolean
ReflectionClass::isSubclassOf(string or ReflectionClass
boolean
class)
ReflectionClass::isUserDefined( ) boolean

ReflectionClass::newInstance(mixed args) object

Table 9-4. ReflectionFunction methods

Name Returns
ReflectionFunction::_ _construct(string name) ReflectionFunction

ReflectionFunction::_ _toString( ) string


ReflectionFunction::export(string name, [, bool return])
mixed
throws Reflection_Exception
ReflectionFunction::getDocComment( ) string

ReflectionFunction::getEndLine( ) int

ReflectionFunction::getFileName( ) string
ReflectionFunction::getName( ) string
Reflection_Parameter[
ReflectionFunction::getParameters( )
]
ReflectionFunction::getStartLine( ) int

ReflectionFunction::getStaticVariables( ) array
ReflectionFunction::invoke(mixed args) mixed

ReflectionFunction::isInternal( ) bool
ReflectionFunction::isUserDefined( ) bool

ReflectionFunction::returnsReference( ) bool

Table 9-5. ReflectionMethod methods


Name Returns
ReflectionMethod::_ _construct(mixed class, string name) ReflectionMethod

ReflectionMethod::_ _toString( ) string

ReflectionMethod::export(mixed class, string name, [, bool


mixed
return]) throws Reflection_Exception

ReflectionMethod::getDeclaringClass( ) Reflection_Class
ReflectionMethod::getDocComment( ) string

ReflectionMethod::getEndLine( ) int

ReflectionMethod::getFileName( ) string

ReflectionMethod::getModifiers( ) int

ReflectionMethod::getName( ) string
Reflection_Parameter[
ReflectionMethod::getParameters( )
]
ReflectionMethod::getStartLine( ) int

ReflectionMethod::getStaticVariables( ) array

ReflectionMethod::invoke(mixed object, mixed args) mixed

ReflectionMethod::isAbstract( ) boolean

ReflectionMethod::isConstructor( ) boolean

ReflectionMethod::isDestructor( ) boolean
ReflectionMethod::isFinal( ) boolean

ReflectionMethod::isInternal( ) boolean
ReflectionMethod::isPrivate( ) boolean
ReflectionMethod::isProtected( ) boolean

ReflectionMethod::isPublic( ) boolean
ReflectionMethod::isStatic( ) boolean

ReflectionMethod::isUserDefined( ) boolean
ReflectionMethod::returnsReference( ) boolean

Table 9-6. ReflectionProperty methods

Name Returns
ReflectionProperty::_ _construct(mixed class, string name) ReflectionProperty
Name Returns
ReflectionProperty::_ _toString( ) string

ReflectionProperty::export(mixed class, string name, [, bool


mixed
return]) throws Reflection_Exception

ReflectionProperty::getDeclaringClass( ) Reflection_Class

ReflectionProperty::getModifiers( ) int
ReflectionProperty::getName( ) string

ReflectionProperty::getValue(object object) mixed

ReflectionProperty::isDefault( ) boolean

ReflectionProperty::isPrivate( ) boolean

ReflectionProperty::isProtected( ) boolean
ReflectionProperty::isPublic( ) boolean

ReflectionProperty::isStatic( ) boolean

ReflectionProperty::setValue(object object, mixed value) void

Table 9-7. ReflectionParameter methods

Name Returns
ReflectionParameter::_ _construct(mixed function, mixed
ReflectionParameter
parameter)
ReflectionParameter::_ _toString( ) string

ReflectionParameter::allowsNull( ) boolean
ReflectionParameter::export(mixed function, mixed parameter,
mixed
[, bool return]) throws Reflection_Exception
ReflectionParameter::getClass( ) string

ReflectionParameter::getName( ) string
ReflectionParameter::isPassedByReference( ) boolean

Table 9-8. ReflectionExtension methods

Name Returns
Name Returns
ReflectionExtension::_ _construct(string name) ReflectionExtension

ReflectionExtension::_ _toString( ) string

ReflectionExtension::export(string name, [, bool return])


mixed
throws Reflection_Exception

ReflectionExtension::getClasses( ) Reflection_Class[ ]
ReflectionExtension::getClassNames( ) array

ReflectionExtension::getConstants( ) array

Reflection_Function[
ReflectionExtension::getFunctions( )
]

ReflectionExtension::getINIEntries( ) array

ReflectionExtension::getName( ) string

ReflectionExtension::getVersion( ) string

< Day Day Up >


< Day Day Up >

Chapter 10. PHP 5 in Action


Previous chapters demonstrate individual pieces of PHP 5, but this chapter is different. Instead of
presenting new material, it combines together multiple features of PHP 5 into a unified program.

Specifically, I present a simple address book application that allows you to add new people and
search for existing people. This application is designed to:

Demonstrate a wide range of new PHP 5 features

Work completely, even though it's limited in scope

Be accessible both over the web and from the command line

Separate application and presentation logic

Records are limited to first name, last name, and email address, and are stored in an SQLite
database. A record can be turned into a Person object, and multiple Persons are stored in an
addressBook object.

Since the application needs to work from both the Web and from the command line, you need an
easy way to convert records into both HTML and plain text. To facilitate this task, every object is
capable of using DOM to create an XML representation of its contents. You then use SimpleXML to
manipulate these XML documents into formatted output.

The first section in this chapter covers the database schema, so you know how the records are
stored. Parts two and three cover the Person and addressBook classes. These sections use many of
PHP 5's new object-oriented features, including property overloading, visibility, class type hints, and
custom object iterators using the IteratorAggregate interface. The classes also create, append, and
import DOM documents and elements.

Now that you've defined how the data is represented, both in the underlying storage mechanism (the
database) and within your program (the Person and addressBook classes), the next step is to
translate the objects into user-visible content.

The fourth section shows how you create a set of templates, one for HTML and another for plain text.
To ensure consistency, you use an abstract class to define the template interface for the page
header, body, and footer. Within the templates, process the XML with SimpleXML.

Finally, you're ready to put together the actual application. However, since you've already created all
the component parts, the assembly process is short. The biggest difficulty is converting input from
both the Web and the command line into a similar format. One piece that is considerably simplified,
however, is error handling through the use of exceptions.

< Day Day Up >


< Day Day Up >

10.1 Defining Your Database Schema


The first version of the address book contains only a person's first name, last name, and email
address. However, you want to leave open the option of adding additional fields at a later time.

You also know that the data is accessed for reading, as you look up existing records. Writes will be
relatively infrequent, but will definitely occur.

Based on those criteria, SQLite is a good choice as a database. Your dataset isn't gigantic, nor do you
need advanced features such as replication. SQLite also retrieves data very quickly, so it's perfect for
pulling records out of the database.

The next step is writing a database schema and creating a new database, as shown inExample 10-1.

Example 10-1. Database schema for address book

$sql = <<< _SQL_

CREATE TABLE people (

id INTEGER PRIMARY KEY,

firstname TEXT,

lastname TEXT,

email TEXT);

CREATE INDEX people_firstname_index ON people(firstname);

CREATE INDEX people_lastname_index ON people(lastname);

CREATE INDEX people_email_index ON people(email);

_SQL_;

$db = new SQLiteDatabase('addressBook.db');

$db->query($sql);

Example 10-1 creates a new database file called addressBook.db and adds a people table with four
fields: id, firstname, lastname, and email.
The id field is an integer and the primary key. The other three fields are simply defined asTEXT.
Since you need to search against the text fields, you create indexes for allfirstname, lastname, and
email fields using the CREATE INDEX command.

See Chapter 4 for more on SQLite, and see Section 4.4.1 in that chapter for more on indexes
specifically.

< Day Day Up >


< Day Day Up >

10.2 The Person Class


The next step is creating Person, an object representation of the data. This class encapsulates the
address book entry's information into an object you can manipulate. You need to be able to create a
new object, get and set information, cycle through all the fields, and convert the object to XML.

10.2.1 Constructor
The class begins with its constructor, as shown in Example 10-2.

Example 10-2. Person::_ _construct( )

class Person implements IteratorAggregate {

private $data;

public function _ _construct($person = NULL) {

$this->data = array('firstname' => '',

'lastname' => '',

'email' => '',

'id' => 0);

if (is_array($person)) {

foreach ($person as $field => $value) {

$this->$field = $value;

}
Example 10-2 initializes the $data property as an array of four elements: firstname, lastname,
email, and id. The first three hold information about a person, and the last one is the record's
unique database key.

When nothing's passed into the constructor, you're left with an emptyPerson object, which can then
be defined. However, you can optionally pass an array of data that contains information about a
person.

When this happens, the constructor loops through the array and converts each element into an
object property. As you'll see, this class actually overloads property access with_ _get( ) and _
_set( ), so the information is really going inside $data.

You've probably noticed that none of the properties are set by name. For example, there's no call to
$this->firstname. Instead, the code references $this->$field.

It's somewhat unusual to refer to a property name with a variable; however, this allows you to avoid
hardcoding property names inside the foreach. In general, you want to write generic code like this,
as it reduces headaches when you modify your code.

The foreach converts an array with any combination of keys into properties. Assume, for instance,
that $person is array('firstname' => 'Rasmus'). Then inside the loop, $field is firstname and
$value is Rasmus. Therefore, $this->$field = $value translates to $this->firstname =
'Rasmus'.

When $person holds additional elements, the foreach automatically takes care of every field in turn
without any action on your part. Don't worry if someone tries to pass in additional array elements,
such as address. These are filtered out inside of _ _set( ).

10.2.2 Encapsulating Property Access with _ _get( ) and _ _set( )


Public properties break encapsulation, so Person implements the property overload methods of _
_get( ) and _ _set( ). The methods in Example 10-3 actually retrieve and store data from the
protected $data property.

Example 10-3. Person::_ _set( ) and Person::_ _get( )

public function _ _set($property, $value) {

if (isset($this->data[$property])) {

$this->data[$property] = $value;

public function _ _get($property) {


if (isset($this->data[$property])) {

return $this->data[$property];

} else {

return false;

The _ _set( ) method doesn't add all properties to $data. It first uses isset( ) to check that
$property already exists within the array. This will return true only for the elements assigned in the
constructor. If $property fails this test, the method discards $value. This check prevents you from
adding arbitrary data to the object, so you can be sure of the class's properties.

The _ _get( ) method behaves in a similar fashion. If the property isset( ), the method returns
the property's value. If it's not set, the method returns false.

See Section 2.6.1 for more on property overloading.

10.2.3 Enabling Custom Object Iteration with getIterator( )


Since you're overloading property access, the object is no longer iterable using PHP's default iteration
behavior. There's only one class property, $data, and its visibility is restricted from public view.

The fix is to implement the IteratorAggregate interface and to write a getIterator( ) method, as
shown in Example 10-4.

Example 10-4. Person::getIterator( )

public function getIterator( ) {

return new ArrayObject($this->data);

This method must return an iterator. Instead of creating a custom iterator, it's better to use SPL's
ArrayObject. This object takes an array, such as $this->data, and converts it into an iterator
object that acts just like an array.

Chapter 6 covers iteration in detail. In particular, see Section 6.8 and Section 6.9.

10.2.4 Converting a Person Object to an XML Document Using DOM


Your final method is toDOM( ), shown in Example 10-5. This method converts Person into a DOM
object for use with the XML functions.

Example 10-5. Person::toDOM( )

public function toDOM( ) {

$xml = new DOMDocument('1.0', 'UTF-8');

$xml->formatOutput = true; // indent elements

$person = $xml->appendChild(new DOMElement('person'));

foreach ($this as $key => $value) {

$person->appendChild(new DOMElement($key, $value));

return $xml;

The goal of Example 10-5 is to create a DOM object and populate it with child elements for each piece
of data stored in the object.

Your first step is instantiating a new DOMDocument. Set the XML version to 1.0 and the encoding to
UTF-8. The second line indents the output with spaces. This makes the XML easier to read for
humans.

Now you create the DOM elements. The root element is a new person. Next, a foreach loop creates
elements for each of the current object's properties.

It's important to loop through $this instead of $this->data. Right now, you happen to store your
data inside the $data property, but that could change. If you do loop through $this->data and then
change this, you would have to update all your methods. Looping through$this invokes
getIterator( ), so as long as you update getIterator( ), it's a seamless change every place
else.

The code uses a feature of the DOMElement constructor. When a second parameter is passed, the
object creates a text node containing $value and places it as a child of the new DOM element.

See Section 5.9 for more details on creating DOM objects.


10.2.5 Creating and Manipulating a Person Object
Example 10-6 shows the Person class in action.

Example 10-6. Creating a new Person

$rasmus = new Person;

$rasmus->firstname = 'Rasmus';

$rasmus->lastname = 'Lerdorf';

$rasmus->email = '[email protected]';

foreach($rasmus as $property => $value) {

print "$value\n";

print $rasmus->toDOM( )->saveXML( );

Example 10-6 creates a new Person, assigns values to the object, and then iterates through it. It
also prints the object's DOM representation. This is the result:

firstname: Rasmus

lastname: Lerdorf

email: [email protected]

id: 0

<?xml version="1.0" encoding="UTF-8"?>

<person>

<firstname>Rasmus</firstname>

<lastname>Lerdorf</lastname>

<email>[email protected]</email>
<id>0</id>

</person>

The properties all hold their assigned values; however, sinceid wasn't changed, it's still at the
default value of 0.

Alternatively, you can pass an array to the constructor:

$info = array('firstname' => 'Zeev',

'lastname' => 'Suraski',

'email' => '[email protected]');

$zeev = new Person($info);

print $zeev->toDOM( )->saveXML( );

<?xml version="1.0" encoding="UTF-8"?>

<person>

<firstname>Zeev</firstname>

<lastname>Suraski</lastname>

<email>[email protected]</email>

<id>0</id>

</person>

Passing an array is functionally equivalent to setting properties one by one, but it's more concise.

< Day Day Up >


< Day Day Up >

10.3 The addressBook Class


Now that you have a Person, it's time to turn to the address book. Just like a physical address book,
an addressBook object holds a collection of information about people.

The addressBook class gives you two actions: you can either add a new record or search existing
records. That's it. Updating and deleting records is left for Version 2.0. However, as always, you want
to design the class so that it's easy to add these methods.

Like Person, addressBook has a toDOM( ) method, which exports an entire collection of people to
XML as a group.

10.3.1 Constructor
Besides instantiating the object, addressBook's constructor connects to the database, as shown in
Example 10-7.

Example 10-7. addressBook::_ _construct( )

class addressBook implements IteratorAggregate {

protected $data;

protected $db;

public function _ _construct( ) {

$this->data = array( );

$this->db = new SQLiteDatabase('addressBook.db');

The constructor opens the SQLite database addressBook.db that was created earlier in Section 10.1.
The result handle is stored in the $db property.

10.3.2 Adding a Person to an addressBook


An empty address book isn't very interesting, so Example 10-8 implements the addPerson( )
method. This method takes a Person object, converts it into an SQL query, and inserts it into the
database.

Example 10-8. addressBook::addPerson( )

public function addPerson(Person $person) {

$data = array( );

foreach ($person as $fields => $value) {

$data[$fields] = "'" . sqlite_escape_string($value) . "'";

$data['id'] = 'NULL';

$sql = 'INSERT INTO people '.

'(' . join(',', array_keys( $data)) . ')' .

'VALUES (' . join(',', array_values($data)) . ');';

if ($this->db->query($sql)) {

$rowid = $this->db->lastInsertRowid( );

$person->id = $rowid;

return $rowid;

} else {

throw new SQLiteException(

sqlite_error_string($this->db->lastError( )),

$this->db->lastError( ));

Since addPerson( ) will work only on a Person object, the argument is type hinted to require a
Person.

This method's main job is converting the Person object into an SQL statement. Like Person::_
_construct( ), the goal is to have little or no Person-specific details inside of addPerson( ).

You want to be able to update Person to include, for example, a cell phone number field, without
modifying addressBook. This reduces the coupling between the classes, which is a major design goal
of object-oriented programming. A class should be able to modified without affecting the behavior of
any of the other classes.

The top half of the method iterates through the fields returned by $person's iterator. It creates an
array whose keys are the object's properties. The array's values are escaped SQLite strings, wrapped
inside single quotation marks.

To ensure that SQLite generates the correct row ID, you explicitly set theid element to NULL. This
prevents someone from assigning their own id and disturbing the auto-increment sequence.

The general syntax for an SQL INSERT statement is INSERT INTO table (field1, field2, ...,
fieldN) VALUES (value1, value2, ..., valueN). So, you need a way to put all the field names
into one comma-separated list and all the values into another.

Fortunately, the $data array's keys are the field names and the array's values are the escaped SQL
values. Therefore, you can extract the desired portion of the array witharray_keys( ) or
array_values( ).

Those arrays are then join( )ed together with commas (,) to create the proper SQL statement. This
algorithm works regardless of the name or number of fields in the database.

If the query succeeds, the new primary key is assigned to $rowid from SQLite's lastInsertRowid(
) method. The method also updates $person to contain the correct value instead of 0. This code
takes advantage of PHP 5's pass-by-reference feature for objects. Without it, the change in$person
would exist only in the local copy within the method. The method returns$rowid on a successful
query or throws an SQLiteException on an error.

Because DOM already throws exceptions, it's cleaner for you to manually throwSQLiteExceptions of
your own. This allows you to process all errors, from DOM and from SQLite, in a consistent manner.

Since the SQLite extension isn't throwing the error, you need to populate the exception's message
and code fields yourself. SQLite's lastError( ) method returns an integer error code that describes
of the problem. You can convert that number into an English description withsqlite_error_string(
). These are the two pieces of data you need to pass when you create the SQLiteException.

Example 10-9 inserts a new Person into a clean addressBook.

Example 10-9. Adding a Person into an addressBook

$rasmus = new Person;

$rasmus->firstname = 'Rasmus';

$rasmus->lastname = 'Lerdorf';
$rasmus->email = '[email protected]';

try {

$ab = new addressBook;

$ab->addPerson($rasmus);

print $rasmus->toDOM( )->saveXML( );

} catch (Exception $e) {

// Error!

The results look like:

<?xml version="1.0" encoding="UTF-8"?>

<person>

<firstname>Rasmus</firstname>

<lastname>Lerdorf</lastname>

<email>[email protected]</email>

<id>1</id>

</person>

As you can see, the id element is 1 instead of the default value of 0.

When there's an SQLite error, such as when the people table does not exist, the addPerson( )
method throws an exception and print $rasmus->toDOM( )->saveXML( ); is never called. Instead,
control immediately jumps to the catch block for error processing. For now, the examples are only
trapping the error, not processing it. Later on, once you assemble the full application, you'll add in
more complete error handling.

10.3.3 Searching for People Within an addressBook


It's boring just to enter people into an address book. The real fun comes when you retrieve them
using search( ), as shown in Example 10-10.
Example 10-10. addressBook::search( )

public function search(Person $person) {

$where = array( );

foreach ($person as $field => $value) {

if (!empty($value)) {

$where[ ] = "$field = '" . sqlite_escape_string($value) . "'";

$sql = 'SELECT * FROM people';

if (count($where)) {

$sql .= ' WHERE ' . join(' AND ', $where);

if ($people = $this->db->query($sql)) {

foreach ($people as $person) {

$this->data[ ] = new Person($person);

return $people->numRows( );

} else {

throw new SQLiteException(

sqlite_error_string($this->db->lastError( )),

$this->db->lastError( ));

}
The method works similarly to addPerson( ), using a foreach loop to build up an SQL statement.
However, unlike an INSERT, a SELECT doesn't require a parallel set of records joined by commas.
Instead, fields and values are separated with an equals sign (=).

To keep searches loose, the WHERE clause doesn't include any empty( ) valued fields. This allows you
to find all the people with a firstname of Rasmus by keeping the other fields as blanks.

Since an empty WHERE clause is illegal, WHERE is appended to $sql only if $where has at least one
element. These elements are then ANDed together using join( ).

The query results are retrieved using the SQLite iterator that fetches rows as associative arrays.
Inside the loop, pass the result array to Person. This creates a new Person object with all the correct
details that are stored in the $data property.

Finally, the method returns the total number of found rows, using thenumRows( ) method of the
SQLite result object. When no rows are found, this is equal to 0.

Example 10-11 shows one search that finds records and another that fails.

Example 10-11. Searching an addressBook

$ab = new addressBook;

$rasmus = new Person;

$rasmus->firstname = 'Rasmus';

print 'Rasmus: ' . $ab->search($rasmus) . "\n";

$zeev = new Person;

$zeev->firstname = 'Zeev';

print 'Zeev: ' . $ab->search($zeev) . "\n";

Rasmus: 1

Zeev: 0

Since you've already inserted Rasmus into the address book back inExample 10-9, the first search
returns 1. However, Zeev is not to be found.
The search( ) method is quite basic. It doesn't allow you to find all people namedRasmus or Zeev in
a single query, for instance. However, you can run two search( )es against the same address book
to create a composite search result.

10.3.4 Converting an addressBook Object to an XML Document Using


DOM
It's not very interesting merely to see the number of matches for your search. What you really want
is access to the information about each person. Like Person, this is accomplished using a combination
of iterators and XML, as shown in Example 10-12.

Example 10-12. addressBook::getIterator( )

public function getIterator( ) {

return new ArrayObject($this->data);

As in Example 10-4, getIterator( ) returns the object's $data property.

Example 10-13 contains the code for the addressBook::toDOM( ) method.

Example 10-13. addressBook::toDOM( )

public function toDOM( ) {

$xml = new DOMDocument('1.0', 'UTF-8');

$xml->formatOutput = true; // indent elements

$ab = $xml->appendChild(new DOMElement('addressBook'));

foreach ($this as $person) {

$p = $person->toDOM( );

$p = $xml->importNode($p->documentElement, true);

$ab->appendChild($p);

return $xml;
}

The toDOM( ) method here acts similarly, but not identically, to the toDOM( ) method in Person. Its
first half is the same, but the second is different.

This method also creates a new DOMDocument and uses the same set of XML versions and document
encodings. It creates a root element, too, but this time it's calledaddressBook instead of person.

Inside the foreach, there's no need to iterate through $person like you did inside Person::toDOM(
). Instead, you can just ask $person to convert itself into a DOM object using its own toDOM( )
method.

However, it's illegal to directly append parts of one DOMDocument to another. You must first convert
the object using DOM's importNode( ) method. The first parameter is the part of the document you
want, and the second indicates whether you want to make a deep or shallow copy. The call in this
example grabs everything from the root node down and does a deep copy. The imported nodes are
then appended to the address book to create a master XML document that contains all the matching
People.

With toDOM( ), you can view the results of your searches in Example 10-14.

Example 10-14. Converting search results to XML

$zeev = new Person;

$zeev->firstname = 'Zeev';

$zeev->lastname = 'Suraski';

$zeev->email = '[email protected]';

$ab = new addressBook;

$ab->addPerson($zeev);

$ab->search(new Person);

print $ab->toDOM( )->saveXML( );

<?xml version="1.0" encoding="UTF-8"?>

<addressBook>

<person>
<firstname>Rasmus</firstname>

<lastname>Lerdorf</lastname>

<email>[email protected]</email>

<id>1</id>

</person>

<person>

<firstname>Zeev</firstname>

<lastname>Suraski</lastname>

<email>[email protected]</email>

<id>2</id>

</person>

</addressBook>

Perfect! Here's an XML document containing all the people in the address book. Additionally, since
neither search( ), getIterator( ), nor toDOM( ) hardcode any details about Person, they're not
affected when you modify the Person class.

< Day Day Up >


< Day Day Up >

10.4 The Template Class


Since the application needs to produce multiple output formats, you need a way to control the
display. For example, the HTML output starts with <html>, but you certainly don't want that to
appear on the command line.

Good programming style says that it's bad form to mix programming and display logic. This leads to
messy code because everything becomes intertwined. Additionally, since you already know you need
a minimum of two different types of output, doing everything inline not only makes it harder to add
additional output styles, but it's also more difficult to maintain your existing styles.

Therefore, you should create template objects. Each object should have the same set of display
methods, such as getHeader( ) and getFooter( ). However, they'll return different content-for
example, HTML or plain text.

This is the perfect place to use an abstract base class. The abstract class specifies the exact names
and prototypes for all your methods. Then, you create one class for each format and make sure that
each of those classes extends the base.

The Template class in Example 10-15 has four methods.

Example 10-15. Template abstract class

abstract class Template {

abstract public function getHeader( );

abstract public function getBody(DOMDocument $dom);

abstract public function getFooter( );

public function printAll(addressBook $ab) {

print $this->getHeader( );

print $this->getBody($ab->toDOM( ));

print $this->getFooter( );

}
Two methods-getHeader( ) and getFooter( )-take no parameters. They display the page header
and footer, respectively.

In between them is getBody( ). It requires a DOMDocument, which should contain an XML address
book full of the information that you want to print.

The last and only nonabstract method, printAll( ), is a convenience method that calls the other
three methods and prints them out.

10.4.1 Creating an HTML Template


The HTML class is mostly static HTML, but the getBody( ) method is generated dynamically, as
shown in Example 10-16.

Example 10-16. htmlTemplate class

class htmlTemplate extends Template {

public function getHeader( ) {

$action = $_SERVER['PHP_SELF'];

$header = <<< _HEADER_

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"

"http://www.w3.org/TR/html4/strict.dtd">

<html>

<head>

<title>Address Book</title>

</head>

<body>

<form action="$action" method="POST">

<select type="select" name="mode">

<option value="add">Add</option>

<option value="search" selected="selected">Search for</option>

</select>
<label for="firstname">First Name</label>

<input type="text" name="firstname" id="firstname" size="8">

<label for="lastname">Last Name</label>

<input type="text" name="lastname" id="lastname" size="14">

<label for="email">Email</label>

<input type="text" name="email" id="email" size="14">

<input type="submit" value="Do it!">

</form>

_HEADER_;

return $header;

public function getBody(DOMDocument $dom) {

$body = "<table>\n";

$people = simplexml_import_dom($dom);

foreach ($people as $person) {

$body .= "<tr>\n<td>" . $person->firstname . ' ' .

$person->lastname . "</td>\n<td>" .

$person->email . "</td></tr>\n";

}
$body .= "</table>\n";

return $body;

public function getFooter( ) {

$footer = "</body>\n</html>\n";

return $footer;

While getHeader( ) is mostly static HTML, it contains an HTML form that allows you to both insert
new people into and search the address book. The form's action is set to $_SERVER['PHP_SELF'],
so your script also processes the form. The input elements are named the same as the properties of
your Person class. This allows you to instantiate a Person object from the form without needing to
munge the input data.

The getBody( ) method in Example 10-16 takes a DOM object, but that doesn't mean it needs to
parse the XML with DOM. SimpleXML is the perfect way to handle this basic type of XML document.

The simplexml_import_dom( ) function converts an object from DOM to SimpleXML. Now it's no
problem to create an HTML table: you iterate though the SimpleXML object usingforeach, turning
every $person into another HTML table row. See Section 5.4 for more on this extension.

10.4.2 Creating a Plain-Text Template


The text template, shown in Example 10-17, is less complex than the HTML version because its
header and footer are empty.

Example 10-17. textTemplate class

class textTemplate extends Template {

private $nameLength;

private $emailLength;
public function _ _construct($nameLength = 30, $emailLength = 30) {

$this->nameLength = (int) $nameLength;

$this->emailLength = (int) $emailLength;

public function getHeader( ) {

return;

public function getBody(DOMDocument $dom) {

// 7 is the size of the column divider

// spacers: "| ", " | ", and " |"

$lineLength = $this->nameLength + $this->emailLength + 7;

$text = str_repeat('-', $lineLength) . "\n";

$people = simplexml_import_dom($dom);

foreach ($people as $person) {

$name = $person->firstname . ' ' . $person->lastname;

$text .= sprintf("| %{$this->nameLength}s

"| %-{$this->emailLength}s |\n",

$name, $person->email);

$text .= str_repeat('-', $lineLength) . "\n";


return $text;

public function getFooter( ) {

return;

This class has a constructor with two optional arguments, $nameLength and $emailLength. These
parameters control the length of the printed name and email columns, respectively. They both default
to 30, but you can adjust them if your data is particularly long or short.

Again, the getBody( ) method is the most interesting of the three methods. Since there's no way to
automatically place data into a table from the command line, the method usesstr_repeat( ) and
sprintf( ) to recreate a table. The calls to str_repeat( ) create the top and bottom table borders.

Inside the foreach, sprintf( ) formats the individual lines. The string that reads | %{$this-
>nameLength}s | %-{$this->emailLength}s |\n has two special codes. The first, %{$this-
>nameLength}s, turns the input into a $this->nameLength character-wide, right-aligned, space-
padded string. The second, %-{$this->emailLength}s, does something similar, but aligns the data
on the left side instead of the right.

10.4.3 Displaying Multiple Output Formats with a Template


Now that you've defined your templates, the next step is printing out address books. This is done in
Example 10-18.

Example 10-18. Printing addressBook results using templates

try {

// Create address book and find all people

$ab = new addressBook;

$ab->search(new Person);

// Create HTML template


$template = new htmlTemplate( );

// Convert address book XML and print results

$template->printAll($ab);

} catch (Exception $e) {

// Error

Figure 10-1 shows the rendered output.

Figure 10-1. HTML version of the address book

Alternatively, setting $template to a new textTemplate produces:

-------------------------------------------------------------------

| Rasmus Lerdorf | [email protected] |

| Zeev Suraski | [email protected] |

-------------------------------------------------------------------

< Day Day Up >


< Day Day Up >

10.5 Assembling the Application


Finally, you can integrate the classes and templates into a complete application. This section creates a
dual web and command-line program that shares the same set of code but abstracts out the details,
such as processing the different sources of input data.

10.5.1 Creating the Web Version


The web version gets its input from a form embedded in the HTML template's header, as shown in
Example 10-19 .

Example 10-19. Web-enabled address book application

// Configure format-specific details

$input = $_POST;

$template = new htmlTemplate;

// Set mode

if (isset($input['mode'])) {

$mode = $input['mode'];

} else {

$mode = false;

try {

// Create address book

$ab = new addressBook;

// Load data into Person

$person = new Person($input);


// Add person, if necessary

if ($mode = = 'add') {

$ab->addPerson($person);

// Return results

$ab->search($person);

// Print page

$template->printAll($ab);

} catch (Exception $e) {

$ob = ob_start( );

print $e;

error_log(ob_get_clean( ));

At the top of Example 10-19 , you set a few configuration variables. Since this is the web version, the
$input comes from $_POST and the $template is an htmlTemplate . Later, you will set $input and
$template to different values in the command-line version.

The $mode variable, which controls whether you should add a new person, is assigned using themode
element of the input source.

Once everything is set up, create the addressBook and Person objects. Use the $input array to
configure Person .

Now, if the $mode is set to add , call addPerson( ) to insert the person into the database. This
converts Person into SQL and makes a new entry.

The next step is populating the address book with results. If you're doing a search,$ab contains all the
matching records. If you're inserting a record, $ab contains the new record.

The last action is the call to print out your template methods using the handyprintAll( ) method
instead of making individual calls.

All that assumes, of course, that everything goes smoothly and you encounter no exceptions. Many of
the underlying methods throw exceptions, some because the extension does so automatically and
some because you're actually throw ing them yourself.

However, here's where the benefit of exceptions shines through. You don't need to worry which
methods do or don't throw exceptions, nor do you need to care if the exception is actually thrown by a
second method called by a first one. Everything propagates up to this location and is caught by your
catch block. You've effectively replaced the necessity to check the return value of every function and
method in your application with five lines:

} catch (Exception $e) {

$ob = ob_start( );

print $e;

error_log(ob_get_clean( ));

As described in Chapter 7 , you can print an exception to get a full set of debugging messages. By
wrapping the print call within a output buffer, you can redirect these lines from the screen to the error
log.

For example, when the SQLite can't find the person database table, you get:

[Thu Jul 22 10:30:14 2004] [error] exception 'SQLiteException' with message

'SQL logic error or missing database' in

/www/www.example.com/addressBook/index.php:48

Stack trace:

#0 /www/www.example.com/addressBook/index.php(48): addressBook->search(Object(Person))

#1 {main}

Sure, you still need to check occasional return values at the component level, i.e., inside individual
methods. However, those checks are all encapsulated within your objects, so they occur at a lower
level. When you assemble a program by combining multiple objects together, which is a higher level of
programming, you only need to handle exceptions.

One thing your code doesn't do is print out any error message to a person using the address book. This
is discussed later on in this chapter, in Section 10.6.

10.5.2 Creating the Command-Line Version


The largest difference between the command-line version and the web version is that you need to
parse command-line options yourself. The easiest way to do this is withPEAR's Console_Getopt class,
as shown in Example 10-20 .
Example 10-20. Command-line-enabled address book application

require_once('Console/Getopt.php');

$opts = new Console_Getopt;

// Accept these four optional command line flags.

$longopts = array('mode=', 'firstname=', 'lastname=', 'email=');

$parsed = $opts->getopt($argv, NULL, $longopts);

// Strip leading "--" from flag name

// Convert from getopt( ) array return format to one like $_POST

foreach ($parsed[0] as $opt) {

$key = substr($opt[0], 2);

$input[$key] = $opt[1];

$template = new textTemplate;

Console_Getopt parses $argv and breaks it apart into option pairs. You specify any or all of four
possible options: --mode , --firstname , --lastname , and --email .

Since getopt( ) doesn't return an array that's formatted like $_REQUEST , you need to rework the
array it returns. This requires you to strip the leading -- from the option name and reorganize how the
information is stored.

After fixing $input , set the $template to new textTemplate .

And now you're done. There's nothing left to modify in the program. This command runs the
command-line version of the address book program, ab.php , and searches for people with a first name
of Rasmus :

$ php ab.php --firstname=Rasmus

-------------------------------------------------------------------
| Rasmus Lerdorf | [email protected] |

-------------------------------------------------------------------

Passing --firstname=Rasmus on the command line is equivalent to setting the form element
firstname to Rasmus .

10.5.3 Creating a Unified Program


The final step is unifying the two versions into a single program. This requires you to differentiate
between the command-line and web versions of PHP.

The function php_sapi_name( ) checks which version of PHP you're running and returns its name.
When you're running under the command-line version, it returns cli . The unified application code
shown in Example 10-21 .

Example 10-21. Unified address book application

// Configure format-specific details

if (php_sapi_name( ) = = 'cli') {

require_once('Console/Getopt.php');

$opts = new Console_Getopt;

$longopts = array('mode=', 'firstname=', 'lastname=', 'email=');

$parsed = $opts->getopt($argv, NULL, $longopts);

foreach($parsed[0] as $opt) {

$key = substr($opt[0], 2);

$input[$key] = $opt[1];

$template = new textTemplate;

} else {

$input = $_REQUEST;
$template = new htmlTemplate;

// Set mode

if (isset($input['mode'])) {

$mode = $input['mode'];

} else {

$mode = false;

try {

// Create address book

$ab = new addressBook;

// Load data into Person

$person = new Person($input);

if ($mode = = 'add') {

$ab->addPerson($person);

// Return results

$ab->search($person);

$template->printAll($ab);

} catch (Exception $e) {

$ob = ob_start( );
print $e;

error_log(ob_get_clean( ));

Now the program will parse $argv and print text when it's run from the command line, but also
produce HTML when executed under a web server.

< Day Day Up >


< Day Day Up >

10.6 Wrap-Up and Future Directions


Although you can write a similar address book application in PHP 4, this version is better
encapsulated, doesn't need to worry about passing objects as references, has simplified error
handling, and makes it easy to use XML.

In PHP 4, there's no concept of visibility, so all object properties and methods are public. However, in
PHP 5, you're able to effectively wall off public properties as protected data, yet still allow people easy
access using _ _get( ) and _ _set( ).

By making _ _set( ) accept only the properties defined in the object constructor, you're able to
limit exactly which properties are and aren't valid. This allows you to iterate through properties,
creating SQL statements and DOM objects, without worrying that you'll encounter some unexpected
data.

Additionally, the application takes advantage of PHP 5's automatic pass-by-reference behavior for
objects. After you insert a new Person into the database, you can update its id property within the
addPerson( ) method and have this change affect the original object.

The program also uses exceptions to simplify error handling. DOM automatically throws exceptions
for any errors mentioned in the DOM specification. SQLite also throws exceptions from its
constructor. By manually throwing a few additional SQLiteExceptions, you consolidate all the error
handling into a single try/catch block.

Finally, the interoperability of the new PHP XML extensions allows you to easily create documents
with DOM, yet access them for printing using SimpleXML. Furthermore, it's no problem to substitute
XSLT for SimpleXML within your templates, or to search an addressBook object using XPath.

However, the address book application created in this chapter is just a start, providing a solid
foundation for future enhancements. For instance, it would be nice to allow people to edit and delete
entries from the address book, or to enlarge the number of fields from beyond first name, last name,
and email address.

Also, as mentioned earlier, this application doesn't print a friendly error message when an exception
is thrown. One solution is to modify Template to provide a getErrorMessage( ) method, which
displays format-appropriate text.

Another nifty feature would be enabling this application as a web service, using either SOAP or REST.
You read about SOAP in Chapter 9. REST is a different approach to web services, where you make
requests using HTTP methods such as GET and POST, and the method type tells the server what
action it should take. For example, GET tells the server you want to retrieve existing data, whereas
POST means you want to update existing data. The server then replies with the results in an XML
document that you can process.

Since the application already uses XML internally, it should be a simple extension to create an
xmlTemplate. For more on REST, see the REST Wiki at http://internet.conveyor.com/RESTwiki/.
Finally, you can add the ability to read and write other address book formats. John E. Simpson has an
article on XML.com that discusses many different XML address book schemas, such as vCard.It's
available at http://www.xml.com/pub/a/2004/03/31/qa.html.

< Day Day Up >


< Day Day Up >

Appendix A. Introduction to XML


XML is the most popular way to store data and exchange information over the Internet. Since so
many languages can read and write XML files, use XML when you want to share data among different
applications and platforms. One of XML's greatest features is its ubiquity.

XML also benefits from being easy to learn. Since XML looks like HTML, web developers are familiar
with its tag-based syntax. However, XML is not HTML. HTML has a fixed set of elements:<a>, <img>,
<h1>, etc. With XML, you have the flexibility to use whatever element names best represent your
data.

When choosing how to represent data, developers seem to fall into one of two camps. Some people
think of XML as a record format, similar to comma-separated files. But instead of separating entries
with newlines and fields with commas, XML provides richer classification options.

Other developers view XML as a document specification format. The onlinePHP Manual
(http://www.php.net/manual) is produced from XML files. The PHP Documentation Team's
documents use tags such as <function>, <parameter>, and <example>. This allows them to release
the manual in multiple formats, including two versions of HTML, Windows CHM help files, and PDF.

< Day Day Up >


< Day Day Up >

A.1 Comparing HTML and XML


HTML and XML are siblings: they are both children of Standard Generalized Markup Language
(SGML). Therefore, an XML document looks somewhat like an HTML page.Example A-1 is an XML
document that represents a basic address book.

Example A-1. Simple address book

<address-book>

<person id="1">

<!--Rasmus Lerdorf-->

<firstname>Rasmus</firstname>

<lastname>Lerdorf</lastname>

<city>Sunnyvale</city>

<state>CA</state>

<email>[email protected]</email>

</person>

<person id="2">

<!--Zeev Suraski-->

<firstname>Zeev</firstname>

<lastname>Suraski</lastname>

<city>Tel Aviv</city>

<state/>

<email>[email protected]</email>

</person>

</address-book>
A.1.1 Similarities
Even though Example A-1 is XML, it looks similar to HTML. There are opening and closing elements,
and these elements can contain text, other elements, or both. Elements can also have attributes,
such as id="1" for the person element.

XML also uses the same syntax as HTML for comments (<!--Rasmus Lerdorf-->) and entities
(&lt;). Like HTML, XML has similar restrictions on ampersands, greater- and less-than signs, and
quotation marks.

A.1.2 Differences
There are a few differences between HTML and XML. First, an XML element must have both an
opening and closing tag. To represent an element without text (also known as anempty element),
place a closing slash at the end of the tag: <img src="php.png" />. You can also have completely
blank elements. For instance, since Zeev Suraski lives in Israel, he doesn't have a U.S. state, so that
element is completely empty: <state/>.

Attributes must have either double or single quotes around their values. You cannot have<person
id=1>.

Elements are also case-sensitive. <email> and <EMAIL> are not identical. To circumvent this
restriction, some XML processors have a case-folding setting. When case folding is enabled, all
elements and attributes are converted to the same case before processing.

XML also has a few features that HTML doesn't, including XML declarations, processing instructions,
and CDATA sections.

A.1.2.1 XML declarations

An XML declaration specifies the XML version and document encoding settings for the file. For
instance:

<?xml version="1.0" encoding="UTF-8" ?>

This example tells the XML processor that the file is an XML Version 1.0 file and its contents are
encoded using UTF-8, a form of Unicode.

An XML declaration appears only at the start of an XML file and is optional. The default values are
those used in the example: Version 1.0 of XML and UTF-8 Unicode encoding.

Internally, libxml2 stores all content as UTF-8. While libxml2 has native support for only a few
encodings, it can optionally hook into your system's iconv library to expand the number of encodings
it can use. Since encoding support varies widely from system to system, see
http://www.xmlsoft.org/encoding.html for more information on how libxml2 handles this issue.
A.1.2.2 Processing instructions

The XML declaration is a specific example of a more general feature known asprocessing instructions
(PIs for short). When you want to pass information to your XML processor, use a PI. For example:

<title><?pdf font="Gill Sans" ?>Upgrading to PHP 5</title>

This informs a pdf formatting script to set the font to Gill Sans.

PIs have this standard syntax:

<?target data ... ?>

The XML declaration shown earlier has a target of xml, and its data is version="1.0"
encoding="UTF-8".

It is uncommon to find an XML document that uses a PI whose target doesn't begin withxml.

A.1.2.3 Character Data sections

One difficulty with using HTML and XML is that you're always forced to callhtmlentities( ) on your
data. XML has a way to indicate that your text should be treated as literal text: a block called a
CDATA, short for Character Data. It's written like this:

<![CDATA[<img src="logo.png" alt="A & P">]]>

You don't need to encode < and & inside a CDATA block, and it cannot contain the sequence ]]>. The
awkward syntax comes from XML's ancestor, SGML.

< Day Day Up >


< Day Day Up >

A.2 Well-Formed XML


For an XML document to be considered valid, it must satisfy the following restrictions:

The document must have only one top-level element. This element is called theroot element.

Every element must have both a start and an end tag.

All attributes must have values, and those values must be quoted.

Elements must not overlap. You cannot use <a><b></a></b>, because the ending </a> tag
comes before </b>.

You must convert &, <, and > to their entity equivalents. You can use htmlentities( ) to
solve this.

When a document meets these rules, it's valid, or well-formed, XML.

< Day Day Up >


< Day Day Up >

A.3 Schemas
When you validate HTML, your file is checked not only to see if it's well-formed, but also that your
markup corresponds to the specification. While your application parses XML instead of HTML, it also
expects data in a certain format. When it gets anything else, it can't work correctly.

Therefore, it's beneficial to create a data specification, or schema, that outlines the layout of the XML
document your program requires. This allows you to check the input XML file against a specification
to see if the XML is not only well-formed, but also valid.

There are three different schema formats: DTDs, XML Schema, and RelaxNG.

DTD

DTDs, short for Document Type Definitions, are the old way to write a schema. They come
from SGML and have a more limited syntax than other formats. They're not written in XML, so
they can be difficult to read. Try to avoid DTDs when you can.

XML Schema

XML Schema is the W3-approved document specification format. XML Schemas are written in
XML, so your XML parser can also validate the schema. For more on XML Schema, see Eric van
der Vlist's XML Schema (O'Reilly) or read the specification at http://www.w3.org/XML/Schema.

RelaxNG

An alternative to XML Schemas is RelaxNG, written by the OASIS group. Its home page is at
http://www.relaxng.org/.

PHP 5 can validate documents against all three formats.

< Day Day Up >


< Day Day Up >

A.4 Transformations
One of XML's great advantages is that you can easily manipulate an XML document into another
format. It could be HTML, PDF, or even another XML document. For instance, you could create an
RSS feed for the articles in your XML-based CMS.

XSLT, short for Extensible Stylesheet Language Transformations, is a W3C-defined language for
modifying XML documents. With XSLT, you can create templates (written, of course, in XML) that act
as a series of instructions for how an XML document provided as input should end up as output.

If you're unfamiliar with XSLT, check out XSLT, by Doug Tidwell, or Sal Mangano's XSLT Cookbook
(both published by O'Reilly). XSLT assumes no knowledge of XSLT, while XSLT Cookbook is more
useful for programmers who want a grab bag of recipes to solve commonly encountered XSLT tasks,
such as renaming attributes and elements. The complete specification is located at
http://www.w3.org/TR/xslt.

< Day Day Up >


< Day Day Up >

A.5 XML Namespaces


XML Namespaces let you place a set of XML elements inside a separate "area" to avoid tag name
clashes. This is an important feature because it allows XML documents to be extended and combined.
Unfortunately, using XML namespaces is tricky. For something that initially seems very
straightforward, there's a surprising amount of explanation required.

A.5.1 Why Use Namespaces?


Using XML Namespaces, developers can work together to define a common set of markup for
different sets of data, such as RSS items, meta-information about pages on the Internet, or books.
When programmers everywhere represent related information using the same set of elements in the
same namespace, then everyone can create powerful applications based on a large set of shared
data.

That's the theory, anyway.

On a more practical side, avoiding tag name clashes is still an issue because it's useful to modify XML
documents. Clashes aren't a problem when everyone is working with a fixed set of elements.
However, you can run into trouble if you allow others to extend a document by adding their own
elements.

For example, you may decide to use <title> to refer to the title of a web page, but your friend used
<title> as the title of a person, such as Mister or Doctor. With XML Namespaces, you can keep
<html:title> distinct from <person:title>.

Some languages have a similar concept, where functions and objects belonging to a package can be
namespaced together. PHP does not support namespaces, which is why you may see PHP function
and class names prefixed with a unique string. For example, the PEAR::DB MySQL module is named
DB_mysql. The leading DB_ means that this class will not conflict with a class named simplymysql.

Another example of namespaces is the domain name system: columbia.com is the Columbia
Sportswear company, while columbia.edu is Columbia University. Both hosts are columbia, but one
lives in the .com namespace and the other lives in .edu.

A.5.2 Syntax
In XML, a namespace name is a string that looks like a URL-for example,
http://www.example.org/namespace/. This URL doesn't have to resolve to an actual web page that
contains information about the namespace, but it can. A namespace is not a URL, but a string that is
formatted the same way as a URL.

This URL-based naming scheme is just a way for people to easily create unique namespaces.
Therefore, it's best only to create namespaces that point to a URL that you control. If everyone does
this, there won't be any namespace conflicts. Technically, you can create a namespace that points at
a location you don't own or use in any way, such as http://www.yahoo.com. This is not invalid, but it
is confusing.

Unlike domain names, there's no official registration process required before you can use a new XML
namespace. All you need to do is define the namespace inside an XML document. That "creates" the
namespace. To do this, add an xmlns attribute to an XML element. For instance:

<tag xmlns:example="http://www.example.com/namespace/">

When an attribute name begins with the string xmlns, you're defining a namespace. The namespace's
name is the value of that attribute. In this case, it's http://www.example.com/namespace/.

A.5.3 Namespace Prefixes


Since URLs are unwieldy, a namespace prefix is used as a substitute for the URL when referring to
elements in a namespace (in an XML document or an XPath query, for example). This prefix comes
after xmlns and a :. The prefix name in the previous example is example. Therefore,
xmlns:example="http://www.example.com/namespace/" not only creates a namespace, but assigns
the token example as a shorthand name for the namespace.

Namespace prefixes can contain letters, numbers, periods, underscores, and hyphens. They must
begin with a letter or underscore, and they can't begin with the stringxml. That sequence is reserved
by XML for XML-related prefixes, such as xmlns.

When you create a namespace using xmlns, the element in which you place the attribute and any
elements or attributes that live below it in your XML document are eligible to live in the namespace.
However, these elements aren't placed there automatically. To actually place an element or attribute
in the namespace, put the namespace prefix and a colon in front of the element name. For example,
to put the element title inside of the http://www.example.com/namespace/ namespace, use an
opening tag of <example:title>.

The entire string example:title is called a qualified name, since you're explicitly mentioning which
element you want. The element or attribute name without the prefix and colon, in this casetitle, is
called the local name.

Note that while the xmlns:example syntax implies that xmlns is a namespace prefix, this is actually
false. The XML specification forbids using any name or prefix that begins withxml, except as detailed
in various XML and XML-related specifications. In this case,xmlns is merely a sign that the name
following the colon (:) is a namespace prefix, not an indication thatxmlns is itself a prefix.

A.5.4 Examples
Example A-2 updates the address book from Example A-1 and places all the elements inside the
http://www.example.com/address-book/ namespace.
Example A-2. Simple address book in a namespace

<ab:address-book xmlns:ab="http://www.example.com/address-book/">

<ab:person id="1">

<ab:firstname>Rasmus</ab:firstname>

<ab:lastname>Lerdorf</ab:lastname>

<ab:city>Sunnyvale</ab:city>

<ab:state>CA</ab:state>

<ab:email>[email protected]</ab:email>

</ab:person>

<!-- more entries here -->

</ab:address-book>

If two XML documents map the same namespace to different prefixes, the elements still live inside
the same namespace. The URL string defines a namespace, not the prefix. Also, two namespaces are
equivalent only if they are identical, including their case. Even if two URLs resolve to the same
location, they're different namespaces.

Therefore, this document is considered identical to Example A-2:

<bigbird:address-book xmlns:bigbird="http://www.example.com/address-book/">

<bigbird:person id="1">

<bigbird:firstname>Rasmus</bigbird:firstname>

<bigbird:lastname>Lerdorf</bigbird:lastname>

<bigbird:city>Sunnyvale</bigbird:city>

<bigbird:state>CA</bigbird:state>

<bigbird:email>[email protected]</bigbird:email>

</bigbird:person>
<!-- more entries here -->

</bigbird:address-book>

The ab prefix has been changed to bigbird, but the namespace is still
http://www.example.com/address-book/. Therefore, an XML parser would treat these documents
as if they were the same.

A.5.5 Default Namespaces


As you can see, prepending a namespace prefix not only becomes tedious, it clutters up your
document. Therefore, XML lets you specify a default namespace. Wherever a default namespace is
applied, nonprefixed elements and attributes automatically live inside the default namespace.

A default namespace definition is similar to that of other namespaces, but you omit the colon and
prefix name:

xmlns="http://www.example.com/namespace/"

This means there's yet another way to rewrite the example:

<address-book xmlns="http://www.example.com/address-book/">

<person id="1">

<firstname>Rasmus</firstname>

<lastname>Lerdorf</lastname>

<city>Sunnyvale</city>

<state>CA</state>

<email>[email protected]</email>

</person>

<!-- more entries here -->

</address-book>

It is not uncommon to find a document that uses multiple namespaces. One is declared the default
namespace, and the others are given prefixes.

For more on XML Namespaces, read Chapter 4 of XML in a Nutshell by Elliotte Rusty Harold and W.
Scott Means (O'Reilly) or see the W3 specification at http://www.w3.org/TR/REC-xml-names/

< Day Day Up >


< Day Day Up >

A.6 XPath
XPath is a W3C standard (http://www.w3.org/TR/xpath) for locating portions of an XML document
that match a set of criteria. Use XPath to find the names of all the people in your XML address book
who live in New York, all URLs for articles written on PHP in a Meerkat RSS feed, or the most recent
entry into your XML-based content management system.

Think of XPath as SQL for XML documents. You can do all kinds of advanced queries using XPath,
such as finding items with a certain parent, attribute, or location in the tree. XPath uses the same
syntax as XSLT, so you might be familiar with parts of it, even if you're not an XPath expert.

There are two parts to an XPath query: the portion of the XML document you wish to retrieve and the
restrictions you want to place upon your query. This is analogous to SQLSELECT and WHERE clauses.

For example, you can search the XML address book in Example A-1 for all the email addresses:

/address-book/person/email

Levels in an XML document are separated by a /, similar to the separators for folders in a directory
path. When the query begins with a slash, it tells XPath to start looking at the top-level element.
Therefore, /address-book/person/email means gather all the email elements under a person
element under an address-book element.

This is like a SQL SELECT without a WHERE. However, if you're planning a trip to Manhattan and just
want to find all your friends who live in New York, NY, use this:

/address-book/person[city = "New York" and state = "NY"]/email

The text inside square brackets refines the XPath query. [city = "New York" and state = "NY"]
restricts the search to entries where the city element under person is New York and the state is
NY.

To check attributes instead of elements, prepend an @:

/address-book/person[@id = "1"]

This finds all persons with an id attribute of 1.

A good book on XPath is John E. Simpson's XPath and XPointer (O'Reilly). John's article "Top Ten Tips
to Using XPath and XPointer" is posted on XML.com at
http://www.xml.com/pub/a/2002/08/14/xpath_tips.html. Additionally, Chapter 9 of XML in a Nutshell
covers XPath and is available online at http://www.oreilly.com/catalog/xmlnut2/chapter/ch09.pdf.

< Day Day Up >


< Day Day Up >

Appendix B. Additional New Features and


Minor Changes
This appendix covers the changes made in PHP 5 that aren't documented elsewhere in the book.
Some sections discuss new features, such as the command-line processing options. Other sections
contain slight modifications of existing features, such as what happens when you incorrectly treat a
string as an array, or how strrpos( ) handles its needle. These fixes will trip you up if you're relying
on PHP 4 behavior.

< Day Day Up >


< Day Day Up >

B.1 Passing Optional Parameters by Reference


You can now pass optional parameters by reference. For example:

function updateAddress(&$address = 'NULL') {

In PHP 4, you could declare a parameter as either optional or pass-by-reference, but not both. PHP 5
removes this limitation.

< Day Day Up >


< Day Day Up >

B.2 New E_STRICT Error Setting


PHP 5's new E_STRICT error setting issues a warning when you use deprecated features. Specifically, it
complains when you:

Create objects without a class definition

Use a constructor named after the class instead of _ _construct( )

Use var instead of public

Use is_a( ) instead of instanceof

Copy an object instead of making a reference (requires zend.ze1_compatibility_mode to be On )

Statically invoke a nonstatic method

Refine the prototype of an inherited method other than _ _construct( )

Return a nonvariable by reference

Assign the object returned by new by reference

All of these features work in PHP 5, but you should slowly modify your code to stop using them. Here are a few
examples:

This code automagically creates $person as an object without a class definition:


$person->name = 'Rasmus Lerdorf';

PHP Strict Standards: Creating default object from empty value

This Person class uses var instead of public (or, even better, private ). It also defines two constructors:
class Person {

var $name;

function _ _construct($name) {

$this->name = $name;

}
function Person($name) {

$this->name = $name;

PHP Strict Standards: var: Deprecated. Please use the public/private/protected modifiers

PHP Strict Standards: Redefining already defined constructor for class Person

E_STRICT is not enabled by default, nor is it part of E_ALL . To enable it, set your error_reporting
configuration directive to E_ALL | E_STRICT . Setting E_STRICT within a PHP script using error_reporting( )
causes PHP to miss some errors because it has already parsed the file.

< Day Day Up >


< Day Day Up >

B.3 Treating Strings as Arrays Causes Errors


PHP 5 complains louder than PHP 4 when you treat a string as an array.

Accessing a multidimensional array element causes a fatal error in PHP 5:

$string = 'string';

print $string[0][0];

PHP Fatal error: Cannot use string offset as an array

You can still access an individual character in a string using array notation, but as of PHP 4, this is
deprecated in favor of curly braces ({ }):

$string = 'string';

// valid, but deprecated

for ($i = 0; $i < strlen($string); $i++) {

print $string[$i];

// valid, and preferred

for ($i = 0; $i < strlen($string); $i++) {

print $string{$i};

string

string

Passing strings to array_merge( ) and other array functions generates a warning in PHP 5:

$string = "string";
$array = array_merge($string, $string);

PHP Warning: array_merge( ): Argument #1 is not an array

PHP Warning: array_merge( ): Argument #2 is not an array

This does not generate any messages in PHP 4.

< Day Day Up >


< Day Day Up >

B.4 CLI Now Allows Individual Line Processing


The PHP 5 command-line version now allows individual line processing, similar to Perl and awk.

Use these flags on the command line to alter PHP's behavior:

-B

Run this code before processing the file. This is similar to auto_prepend_file.

-R

Run this code on each line of the file.

-E

Run this code at the end of processing the file. This is similar to auto_append_file.

The contents of the current line are available in $argn. This simple example prints each line of
example.txt:

$ php -R 'print "$argn\n" ;' < example.txt

The current line number is stored in $argi. The first line of the file is line 1 (not 0). So, to print out
select lines of a file, do this:

$ php -B '$start = 2; $stop = 4;' -R 'if ($argi >= $start &&

$argi <= $stop) print "$argn\n";' < example.txt


2

Modifying standard input disrupts the flow. For instance:

$ php -R 'print fgets(STDIN);' < example.txt

Using the -R flag causes PHP to read in one line from standard input. Therefore, when you call
fgets( ), you read in a second line. This results in "missing" lines because both PHP and you are
processing the file.

< Day Day Up >


< Day Day Up >

B.5 CLI Always Provides argv and argc


The $argv and $argc variables are always available in the command-line version of PHP 5. This
happens even if the register_argc_argv configuration setting is set to Off.

The $_SERVER superglobal variable also contains entries for $_SERVER['argv'] and
$_SERVER['argc'] if your variables_order configuration directive contains an S to enable this
variable.

< Day Day Up >


< Day Day Up >

B.6 Oracle (oci8) Extension Functions Renamed


The Oracle extension's function names are now consistent with the rest of PHP. For instance,
ocibindbyname( ) is now oci_bind_by_name( ), and ocinewcursor( ) is now oci_new_cursor(
). See Table B-1 for a complete list of old and new function names.

The old names are preserved for backward compatibility, but should be
considered deprecated.

Table B-1. Renamed oci functions

PHP 4 PHP 5
ocibindbyname( ) oci_bind_by_name( )

ocicancel( ) oci_cancel( )

ocicollappend( ) oci_collection_append( )

ocicollassignelem( ) oci_collection_element_assign( )
ocicollgetelem( ) oci_collection_element_get( )

ocicollmax( ) oci_collection_max( )
ocicollsize( ) oci_collection_size( )

ocicolltrim( ) oci_collection_trim( )
ocicolumnisnull( ) oci_field_is_null( )
ocicolumnname( ) oci_field_name( )

ocicolumnprecision( ) oci_field_precision( )
ocicolumnscale( ) oci_field_scale( )

ocicolumnsize( ) oci_field_size( )
ocicolumntype( ) oci_field_type( )
ocicolumntyperaw( ) oci_field_type_raw( )

ocicommit( ) oci_commit( )
ocidefinebyname( ) oci_define_by_name( )
PHP 4 PHP 5
ocierror( ) oci_error( )

ociexecute( ) oci_execute( )

ocifetch( ) oci_fetch( )
ocifetchstatement( ) oci_fetch_all( )

ocifreecollection( ) oci_free_collection( )

ocifreecursor( ) oci_free_cursor( )

ocifreedesc( ) oci_free_descriptor( )
ocifreestatement( ) oci_free_statement( )

ociinternaldebug( ) oci_internal_debug( )

ociloadlob( ) oci_lob_load( )

ocilogoff( ) oci_close( )

ocilogon( ) oci_connect( )

ocinewcollection( ) oci_new_collection( )
ocinewcursor( ) oci_new_cursor( )

ocinewdescriptor( ) oci_new_descriptor( )

ocinlogon( ) oci_new_connect( )

ocinumcols( ) oci_num_fields( )
ociparse( ) oci_parse( )

ocipasswordchange( ) oci_password_change( )
ociplogon( ) oci_pconnect( )

ociresult( ) oci_result( )
ocirollback( ) oci_rollback( )
ocirowcount( ) oci_num_rows( )

ocisavelob( ) oci_lob_save( )
ocisavelobfile( ) oci_lob_import( )

ociserverversion( ) oci_server_version( )
ocisetprefetch( ) oci_set_prefetch( )

ocistatementtype( ) oci_statement_type( )
ociwritelobtofile( ) oci_lob_export( )
< Day Day Up >
< Day Day Up >

B.7 New Configuration Directives


Besides zend.ze1_compatibility_mode, PHP 5 has some new configuration directives:

mail.force_extra_parameters

Extra parameters to always pass to the sendmail program, such as -oi and -t. This setting
overrides the options provided as the fifth argument of mail( ). It defaults to the empty
string.

register_long_arrays

Whether to register the deprecated input arrays, such as $HTTP_POST_VARS and


$HTTP_GET_VARS, in addition to $_POST and $_GET. This should be enabled only for legacy
code. This is set to On in php.ini-dist and to Off in php.ini-recommended.

session.hash_function

Sets the hash function used by the session module. A value of0 means use MD5, a 128-bit
algorithm, and a value of 1 means use SHA-1, a 160-bit algorithm. It defaults to 0.

session.hash_bits_per_character

Sets the number of bits stored per character. Possible values are 4, 5, and 6; the default value
is 4.

< Day Day Up >


< Day Day Up >

B.8 Updated COM Extension


The COM extension has undergone a rewrite to fix bugs and to take advantage of the new features in
PHP 5. For example, COM errors now trigger exceptions. Additionally, the new extension supports
.NET assemblies. This extension is available only on Windows.

For more information, see http://www.php.net/com and http://www.zend.com/php5/articles/php5-


dotnet.php.

< Day Day Up >


< Day Day Up >

B.9 Apache 2 Correctly Sets PATH_TRANSLATED


According to the CGI specification, PATH_TRANSLATED should be set only when PATH_INFO has a
value.

The PHP Apache 1.x module always sets PATH_TRANSLATED, regardless of PATH_INFO. The Apache 2.x
module correctly obeys the specification.

To get the actual path to the script, use SCRIPT_FILENAME instead.

< Day Day Up >


< Day Day Up >

B.10 strrpos( ) Uses the Entire Needle


In PHP 4, strpos( ) uses an entire string as a needle, whereas strrpos( ) limits the needle to a
single character. If you pass strrpos( ) a string of more than one character, it silently uses only the
first character.

In PHP 5, strrpos( ) and strripos( ) now behave identically to strpos( ), in that they find
strings instead of just the first character.

< Day Day Up >


< Day Day Up >

B.11 Windows 95 Support Dropped


PHP 5 does not support Windows 95. You must use a more recent version, such as Windows XP or
Windows 2000, if you want to run PHP 5 under Windows.

< Day Day Up >


< Day Day Up >

B.12 old_function Eliminated


PHP/FI had a quirky function declaration syntax:

function sum $a, $b (

return($a + $b);

);

This was changed in PHP 3, but you could continue to use the old form in PHP 3 and PHP 4 if you
declared your function as an old_function:

old_function sum $a, $b (

return($a + $b);

);

Alas, after six years, this backward compatibility feature is now gone. Another nostalgic remnant of
PHP/FI has passed away.

< Day Day Up >


< Day Day Up >

Appendix C. Installing PHP 5 Alongside


PHP 4
A major difficulty in migrating from PHP 4 to PHP 5 is testing code under PHP 5 without giving up PHP
4. You cannot install both Apache modules on the same web server, but it's possible to install one as
a CGI. This allows you to experiment with PHP 5 and debug your applications without giving up PHP 4
in the process.

The CGI version of PHP is less efficient than the module version and has a few limitations, such as the
inability to hook into Apache's HTTP Basic Authentication mechanism. Nevertheless, this is a good
way to start out using PHP 5. When you're just testing scripts, performance is less of a problem.
When most of your code is PHP 5-ready, you can then install the module version of PHP 5 for final
testing and benchmarking and run PHP 4 as a CGI.

This appendix is more technical than other parts of the book. It assumes you're comfortable installing
PHP from source code and have root access on your machine. It also assumes you're using PHP with
the Apache web server. If you're running a different server, you must translate these instructions
accordingly.

< Day Day Up >


< Day Day Up >

C.1 General PHP 5 Configuration


Since you're already using PHP, this section does not repeat the full set of PHP installation and
configuration instructions. If you get stuck, information for many web servers and operating systems
is available at http://www.php.net/install.

Instead of detailed instructions, this section provides a brief recap of the install process and a list of
the new PHP 5 configuration options. In particular, there have been lots of changes in the MySQL
extension and in all the XML extensions.

C.1.1 Basics
The latest version of PHP is always available from http://www.php.net/downloads.php. Unix
installation basics are unchanged:

1. $ gunzip php-5.x.x.tar.gz

2. $ tar xvf php-5.x.x.tar

3. $ cd php-5.x.x

4. $ ./configure

5. $ make

6. # make install

7. # apachectl restart

The ./configure command will require additional parameters depending on your setup. These are
discussed in more detail in the individual sections that follow.

You will probably need to run the make install and apachectl restart commands as the root user
or superuser.

Windows users have two choices: they can use either the prebuilt binaries or the new Windows build
system. If you don't want to customize PHP yourself, be sure to read the laterSection C.3 for
Windows-specific information.

People who are interested in building PHP on Windows should read theREADME.WIN32-BUILD-
SYSTEM file in the top level of the PHP directory.

C.1.2 Configuration Options


PHP 5 has many new (or modified) extensions. In order to enable them properly, you must use some
new configuration options. They break down into three main groups: XML, databases, and others.

C.1.2.1 XML

Since PHP 5 uses libxml2, many of the PHP 4 configuration options have switched. Table C-1 lists
the differences.

Table C-1. XML extension configuration options

Feature PHP 4 PHP 5


--disable-libxml--with-libxml-
libxml2 N/A
dir=DIR

--with-dom=[DIR]--with-dom-xslt=[DIR]--
DOM --disable-dom
with-dom-exslt=[DIR]

SimpleXML N/A --disable-simplexml

SOAP N/A --enable-soap

XSLT --enable-xslt --with-xslt-sablot=<DIR> --with-xsl=DIR

--disable-xml --with-libexpat-
XML --disable-xml --with-expat-dir=<DIR>
dir=DIR (deprecated)

The largest difference is that PHP 5 really wants you to have a copy oflibxml2 on your system. If
PHP cannot find it, specify its path using --with-libxml-dir=DIR.

Other XML extensions, including DOM and SimpleXML, are now enabled by default and will reuse the
libxml2 path, so it only needs to be set once for the entire configuration process.

The SAX XML extension now also uses libxml2, but you can use the old expat libraries if you insist.
However, you must install them yourself: they are no longer bundled with PHP.

XSLT has switched from Sablotron to libxslt, so you may need to install libxslt to continue using
XSLT.

C.1.2.2 Databases

The biggest disruption in PHP 5 is the unbundling of the MySQL client libraries. Therefore, you must
download and install them yourself. Additionally, there's a newmysqli extension, which you should
use for MySQL 4.1 and above.

A list of new database options appears in Table C-2.


Table C-2. Database extension configuration options

Feature PHP 4 PHP 5


MySQL --with-mysql[=DIR] --with-mysql=DIR

MySQLi N/A --with-mysqli=FILE

SQLite N/A --without-sqlite--enable-sqlite-utf8

Since the MySQL libraries aren't included with PHP, you must pass a path to--with-mysql to specify
their location. For MySQLi, you now specify the location of themysql_config file instead of the
installation directory. MySQL extracts your setup information from this file.

Remember, MySQLi works only with MySQL 4.1.2 and above. If you're usingboth the mysql and
mysqli extensions, configure them against the same version of MySQL to minimize problems.

SQLite is now bundled and is automatically enabled.

C.1.2.3 Other options

PHP 5 also bundles a few completely new extensions and removes some obsolete and experimental
ones. Table C-3 contains a rundown of the major changes.

Table C-3. Other configuration options

Feature PHP 4 PHP 5

Overload --disable-overload N/A


SPL N/A --disable-spl

Tidy N/A --with-tidy=DIR

Since overloading is now a built-in feature, there's no need to enable this as an extension. SPL is
bundled and enabled by default. It requires no additional libraries. Tidy is bundled, but not enabled by
default. You need to install Tidy to use the extension.

< Day Day Up >


< Day Day Up >

C.2 Module and CGI


Installing PHP 5 as a CGI is a quick way to get up and running without sacrificing PHP 4. This section
describes two techniques for making PHP 5 parse your code, instead of PHP 4.

Remember that the CGI version of PHP has a few limitations compared to the Apache module
version. It cannot hook into HTTP Basic Authentication, nor can you read and write internal Apache
values using functions such as apache_note( ).

One option is to enable PHP 5 on a directory-by-directory basis. This lets the PHP 4 module handle
scripts by default, but lets you slowly release PHP 5 code when it's ready.

The second alternative is to enable PHP 5 for all files but on another port. By default, Apache listens
for requests on port 80, so normal requests are still handled as usual. However, whenever a request
is made to your site on port 8080, Apache now hands the script off to PHP 5. With this setup, you can
easily get a complete overview of your PHP 5 compatibility without sacrificing PHP 4 support.

This process breaks down into two main parts: configuring PHP 5 and configuring Apache. The PHP 5
configuration is identical for either Apache setup, so you can easily switch between the two.

C.2.1 Configuring PHP 5 as a CGI


Installing PHP 5 as a CGI requires four additional steps beyond your normal configuration:

1. Remove --with-apxs.

2. Add --enable-force-cgi-redirect.

3. Add --prefix=/usr/local/php5.

4. Customize php.ini.

PHP defaults to a CGI installation, but it won't build it when you specify a web server module.
Therefore, it's important to remove the call to --with-apxs (or whatever your specific web server
is). Also, for security reasons, you should always add --enable-force-cgi-redirect when building
PHP as a CGI. This prevents people from directly accessing your PHP CGI.

When adding PHP 5 to your system, it's important not to overwrite the PHP 4 files and configuration
data. The two must coexist because you're not ready to eliminate PHP 4. Use the--prefix
configuration option to force PHP to install PHP 5 in a separate directory hierarchy. The example in
this section uses /usr/local/php5, but any directory will work. Remember this location because you
need to use it to let Apache know the location of the PHP CGI. After adding this to your PHP
configuration, make and install PHP as normal.

The final step is customizing your php.ini file. First, copy php.ini-recommended to
/usr/local/php5/lib/php.ini. PHP 5 has new configuration options, so you should use this file as your
base. Then, migrate over any changes you've made to your PHP 4 php.ini file to the PHP 5 version.

C.2.2 Directories
The first step to enabling PHP 5 on a directory-by-directory basis is creating a mapping between a
location on your web server and the PHP CGI. To do this, add the following commands to your
Apache httpd.conf file:

ScriptAlias /php5 /usr/local/php5/bin

<Directory /usr/local/php5/bin/>

Options +ExecCGI +FollowSymLinks

AllowOverride None

</Directory>

The ScriptAlias line links the web server's /php5 directory and /usr/local/php5/bin on your
machine. (If you've installed PHP 5 someplace else, alter this line accordingly.) Now, whenever you
access a file from http://www.example.com/php5, Apache executes the file stored in
/usr/local/php5/bin instead.

With this redirection established, the next step is to force Apache to route files ending with a
particular extension to the PHP CGI. You want to limit this, however, to specific directories. This can
be done in one of two ways: placing the information inside a<Directory> section in httpd.conf or
adding it to a .htaccess file.

Here's how to do this inside httpd.conf for all files inside the directory
/www/www.example.com/php5-folder:

<Directory /www/www.example.com/php5-folder>

AddHandler php-cgi-script .php

Action php-cgi-script /php5/php

Options +ExecCGI

</Directory>

The AddHandler directive says that any file ending in .php should be considered a php-cgi-script.
The term php-cgi-script isn't a special name, but merely a unique way to identify the files to be
parsed by PHP 5. You can place additional extensions on this line, like so:

AddHandler php-cgi-script .php .html


Now files ending in .php or .html will be parsed.

The second line adds an Action directive. This directive tell Apache that all php-cgi-scripts should
be handled by the file located at /php5/php. Since you've previously said that all files in /php5 go to
/usr/local/php5/bin, this command effectively passes the script to /usr/local/php5/bin/php. By a
happy coincidence, this is the location where PHP 5 installed the CGI.

Last, since you're running PHP 5 as a CGI script, you need to add +ExecCGI to your Apache Options
to make the script execute.

An alternative approach is to place the middle three lines inside a.htaccess file. This is a special file
read by Apache where you can modify a limited set of Apache (and PHP) settings. There's no need to
wrap them inside a <Directory> section, because Apache automatically assumes the current
location.

Using .htaccess allows you to modify settings without restarting Apache; however, it requires you to
enable the AllowOverride option. This can slow down Apache because it now must check for and
parse these files on every request.

C.2.3 Ports
Another way to provide simultaneous PHP 4 and PHP 5 support is to run PHP 4 on one port and PHP 5
on the other. This is best used for internal debugging on development and staging servers, to allow
programmers to easily check the status of a program under PHP 5.

The first step is to add a Listen directive to httpd.conf:

Listen 80

Listen 8080

By default, Apache (like all web servers) listens on port 80. These lines tell Apache to also listen on
port 8080. Again, there's nothing particular about the number 8080, except that it's greater than
1024 and doesn't conflict with another program. (Port numbers lower than 1024 are reserved for
official use.) Many people choose 8080 because it's a doubling of the official HTTP port.

Now you need to set up the CGI on port 8080. This is done using anApache virtual host:

<VirtualHost _default_:8080>

ScriptAlias /php5 /usr/local/php5/bin

<Directory /usr/local/php5/bin/>

Options +ExecCGI +FollowSymLinks

AllowOverride None

</Directory>
AddHandler php-cgi-script .php

Action php-cgi-script /php5/php

Options +ExecCGI

</VirtualHost>

These commands are identical to the ones in the previous section, except that they're applied to all
files, not just ones in a particular directory or directories.

This <VirtualHost> section matches all requests handled by Apache running on 8080 that aren't
already processed by a different virtual host. When you're running multiple virtual hosts, you should
substitute the hostname for _default_.

Also, if you're using multiple virtual hosts, you need to copy options, such asDocumentRoot, from the
PHP 4 virtual host to the PHP 5 host. For example:

<VirtualHost www.example.com:80>

# Virtual Host Specific Options

DocumentRoot /www/www.example.com

# Load PHP 4 Apache Module

LoadModule php4_module /usr/lib/apache/1.3/libphp4.so

</VirtualHost>

<VirtualHost www.example.com:8080>

# Repeated Configuration Options

DocumentRoot /www/www.example.com

# Add PHP 5 CGI Script

ScriptAlias /php5 /usr/local/php5/bin

<Directory /usr/local/php5/bin/>

Options +ExecCGI +FollowSymLinks


AllowOverride None

</Directory>

AddHandler php-cgi-script .php

Action php-cgi-script /php5/php

</VirtualHost>

When you go to any page under http://www.example.com/, you still see the PHP 4 version. However,
http://www.example.com:8080/ now delivers a PHP 5 version.

< Day Day Up >


< Day Day Up >

C.3 Windows
If you're running Windows, it's not easy to compile PHP, and you're probably already using PHP 4 as
a CGI. Here's how to integrate a precompiled version of PHP 5 into an existing Apache setup:

1. Download and install a precompiled Windows zip package.

2. Configure your web server to handle PHP 5.

3. Set an alternative php.ini location.

If you're using another web server, you will need to modify the configuration directions.

The first step is downloading and unzipping PHP 5. You can download Windows zip packages from
http://www.php.net/downloads.php. (Be sure to grab the "zip" package instead of the installer.)

After you unzip the package, move it to a directory that is similar to your existing PHP 4 installation.
On many systems, PHP 4 is at C:/PHP/, so these instructions place PHP 5 in C:/PHP5/.

Copy php.ini-recommended to php.ini and keep it in C:/PHP5/. Your pre-existing php.ini file is already
located in someplace like C:/WINNT/, but you're not ready to overwrite it yet. Make whatever server-
specific customizations you need to the new php.ini file.

Now you need to modify your web server configuration file to handle PHP 5. If you're running PHP 4
as a CGI, you should already have these three lines:

ScriptAlias /php/ "C:/PHP/"

Action application/x-httpd-php /php/php.exe

AddType application/x-httpd-php .php

This makes the file located at /php/php.exe under the document root execute files ending in .php.
The ScriptAlias directive links this location to C:/PHP/php.exe on your filesystem.

Supplement this with one additional line to create another ScriptAlias, this time for PHP 5:

ScriptAlias /php5/ "C:/PHP5/"

This entry lets you hand off PHP files to the PHP 5 CGI. If you've placed PHP 5 in a different location,
change C:/PHP5/ accordingly.

Now you're all set to override Apache to make PHP 5 process the files instead of PHP 4. Like the
earlier examples in Section C.2, you can do this on either a directory-specific level or a virtual-host
level. For example, for a directory:
<Directory "C:/Program Files/Apache Group/Apache/htdocs/php5-folder">

Action application/x-httpd-php /php5/php-cgi.exe

SetEnv PHPRC "C:/PHP5/"

</Directory>

The Action directive tells Apache to map PHP files to /php5/php-cgi.exe. The directory name, /php5/,
is the new ScriptAlias location, and php-cgi.exe is the new name for the PHP 5 CGI binary. The
unadorned php.exe is now used by the command-line version of PHP.

There's still the job of making PHP 5 use the correct php.ini file. PHP checks a variety of locations by
default, which is bad because that's where your PHP 4 php.ini is stored. Because you used a prebuilt
package, you couldn't alter this path during the configuration process.

Fortunately, you can still control where PHP searches for its php.ini file. When you set the PHPRC
environment variable, PHP looks inside that directory for a php.ini file. Earlier, you created a
configuration file in C:/PHP5/, so that's where you point PHPRC. You cannot specify a filename for
PHPRC, only a directory.

This setup works with all the other configuration examples listed earlier in this appendix. You simply
need to place the two lines just shown inside whatever Apache directive orhttpd.conf file you want.

< Day Day Up >


< Day Day Up >

Colophon
Our look is the result of reader comments, our own experimentation, and feedback from distribution
channels. Distinctive covers complement our distinctive approach to technical topics, breathing
personality and life into potentially dry subjects.

The animals on the cover of Upgrading to PHP 5 are Galapagos tortoises (Geochelone elephantopus).
These giant tortoises, native to the Galapagos Islands, are the largest in the world. They can weigh
up to 500 pounds and measure up to 6 feet from head to tail. As their bulk suggests, they are slow-
moving animals, with a top speed of 0.16 mph. Their plodding pace applies to more than just their
gait-they can live for 200 years and take 20 to 25 years to reach full maturity. Baby tortoises spend
a full month digging out of their sandy nests after they hatch.

Galapagos tortoises are herbivores, and their strong, curved mouths allow them to eat the spiny
vegetation found on the more arid islands in the Galapagos chain. Their slow metabolism allows them
to survive for long periods of time without food or water, which is necessary during the dry season.
Their scaly feet help them navigate the islands' rough lava terrain.

Fifteen subspecies of Geochelone elephantopus have been found in the Galapagos Islands, an
archipelago located about 600 miles off the coast of Ecuador. The different subspecies are identified
primarily by their shell morphology: saddle-back and domed shells are the two general types, but
there are intermediate variations. The subspecies descend from a common ancestor, but developed
their unique characteristics in response to the varied terrain, available food, humidity, and other
environmental factors found on the islands. Charles Darwin's observations of Galapagos tortoises and
how they adapted to their environments helped him formulate his theory of natural selection.

Galapagos tortoises are endangered, and several subspecies are already extinct. Before whalers, seal
fur hunters, and colonists arrived in the 18th century, about 250,000 tortoises lived on the islands.
Today, only 15,000 remain.

Genevieve d'Entremont was the production editor and copyeditor for Upgrading to PHP 5. Sada
Preisch proofread the book. Sarah Sherman and Claire Cloutier provided quality control. Angela
Howard wrote the index.

Ellie Volckhausen designed the cover of this book, based on a series design by Edie Freedman. The
cover image is a 19th-century engraving from the Dover Pictorial Archive. Emma Colby produced the
cover layout with QuarkXPress 4.1 using Adobe's ITC Garamond font.

Melanie Wang designed the interior layout, based on a series design by David Futato. This book was
converted by Joe Wizda to FrameMaker 5.5.6 with a format conversion tool created by Erik Ray,
Jason McIntosh, Neil Walls, and Mike Sierra that uses Perl and XML technologies. The text font is
Linotype Birka; the heading font is Adobe Myriad Condensed; and the code font is LucasFont's
TheSans Mono Condensed. The illustrations that appear in the book were produced by Robert
Romano and Jessamyn Read using Macromedia FreeHand 9 and Adobe Photoshop 6. The tip and
warning icons were drawn by Christopher Bing. This colophon was written by Genevieve d'Entremont.

The online edition of this book was created by the Safari production group (John Chodacki, Becki
Maisch, and Ellie Cutler) using a set of Frame-to-XML conversion and cleanup tools written and
maintained by Erik Ray, Benn Salter, John Chodacki, Ellie Cutler, and Jeff Liggett.
< Day Day Up >
< Day Day Up >

[ SYMBOL] [ A ] [ B ] [ C ] [ D] [ E] [ F] [ G ] [ H] [ I ] [ J] [ K ] [ L] [ M] [ N] [ O ] [ P ] [ Q ] [ R ] [ S ] [ T] [ U] [ V ] [ W] [ X ] [ Z ]

< Day Day Up >


< Day Day Up >

[ SYMBOL] [ A ] [ B ] [ C ] [ D] [ E] [ F] [ G ] [ H] [ I ] [ J] [ K ] [ L] [ M] [ N] [ O ] [ P ] [ Q ] [ R ] [ S ] [ T] [ U] [ V ] [ W] [ X ] [ Z ]

(slash)
" (double quotes)
around attributes in XML
automatically inserted for string parameters
escaping in queries 2nd 3rd
& (ampersand)
preceding entities in XML
preceding variable to pass by reference
<?php ... ?> markers, enclosing PHP program
' (single quotes)
around attributes in XML
automatically inserted for string parameters
escaping in queries 2nd 3rd
// (double slashes), in HTTP request
/ (slash)
empty element in XML
: (colon)
in Tidy configuration file
preceding namespace prefix
:: (double colons)
referencing static methods
referencing static properties
=== (strict equality operator)
? (question mark), substituting for query variables
@ (at sign)
in HTTP request
preceding attributes in XPath
@@ (double at signs), preceding MySQL-specific variables
$_SERVER superglobal variable

< Day Day Up >


< Day Day Up >

[ SYMBOL] [ A ] [ B ] [ C ] [ D] [ E] [ F] [ G ] [ H] [ I ] [ J] [ K ] [ L] [ M] [ N] [ O ] [ P ] [ Q ] [ R ] [ S ] [ T] [ U] [ V ] [ W] [ X ] [ Z ]

abstract classes
abstract keyword
abstract methods
accept( ) method, FilterIterator class
accessors
__get( ) and __set( )
properties as alternative to
public
address book application example 2nd 3rd
addressBook class
assembling application for
command-line version
creating with DOM
database schema for
filtering results of
future enhancement possibilities
Person class
reading into a tree
reading with namespaces 2nd
searching with XPath
Template
web-enabled version
XML documents for
addressBook class, address book application
ADOdb
Advanced PHP Programming (Schlossnagle)
aggregate functions, subselects and
aggregate UDFs 2nd
aggregation
aliases, table
allow_self_signed option, https wrapper
allow_url_fopen configuration directive
always_populate_raw_post_data configuration directive
ampersand (&)
preceding entities in XML
preceding variable to pass by reference
Apache
configuration for enabling PHP directory-by-directory
limitations with CGI version of PHP
PATH_TRANSLATED setting for
running two versions of
virtual hosts, configuring alternate port using
append_child( ) method
appendChild( ) method
application, example of [See address book application example]
$argc variable
$argi variable
$argn variable
$argv variable
array_merge( ) function
ArrayIterator class
ArrayObject class
arrayQuery( ) method
arrays
binding elements of
iterating 2nd
strings treated as
-> (arrow), invoking method
arrow (->), invoking method
asXML( ) method
at sign (@)
in HTTP request
preceding attributes in XPath
at signs, double (@@), preceding MySQL-specific variables
Attribute Declaration node type
Attribute node type
attribute nodes, DOM
attributes, XML 2nd
authentication [See password]
auto_prepend_file configuration directive
AUTOCOMMIT for transactions
__autoload( ) method
autoload feature for undefined classes
Axmark, David (MySQL Reference Manual)

< Day Day Up >


< Day Day Up >

[ SYMBOL] [ A ] [ B] [ C ] [ D] [ E] [ F] [ G ] [ H] [ I ] [ J] [ K ] [ L] [ M] [ N] [ O ] [ P ] [ Q ] [ R ] [ S ] [ T] [ U] [ V ] [ W] [ X ] [ Z ]

-B flag, PHP command line


backtraces 2nd
Exception class providing access to
redirecting output to a file
Balling, Derek J. (High Performance MySQL) 2nd
base class
BEGIN keyword
MySQL
SQLite
binary data, SQLite and
bind_param( ) method 2nd
bind_result( ) method 2nd
binding parameters
array elements
input parameters 2nd
output parameters 2nd
books
about MySQL 2nd
about PEAR SOAP classes
about PHP 2nd
about regular expressions
about SOAP
about web security
about WSDL
about XML
about XML namespaces
about XPath
about XSLT
boolean stream_open( ) method, for wrappers

< Day Day Up >


< Day Day Up >

[ SYMBOL] [ A ] [ B ] [ C] [ D] [ E] [ F] [ G ] [ H] [ I ] [ J] [ K ] [ L] [ M] [ N] [ O ] [ P ] [ Q ] [ R ] [ S ] [ T] [ U] [ V ] [ W] [ X ] [ Z ]

CachingIterator class
CachingRecursiveIterator class
cafile option, https wrapper
__call( ) method 2nd 3rd
call_user_func_array( ) method
capitalization of class names
case-sensitivity
class names
XML elements
catch block
CDATA (Character Data)
sections, creating
sections, node type for
Cerami, Ethan (Web Services Essentials)
CGI version of PHP 2nd
chaining filters 2nd
chaining iterators
Character Data [See CDATA]
character sets, converting
child class
child_nodes( ) method
childNodes property
class keyword
classes [See also specific classes or objects] 2nd
abstract
base
child
creating instances of 2nd
defining 2nd
defining before creating instances of
extending with inheritance
final
hierarchies of
iterating 2nd
parent
properties of
super
client code errors
__clone( ) method 2nd
clone operator
cloning objects 2nd
closedir( ) function
code examples [See also address book application example]
conventions used in
permission to use, when required
colon (:)
in Tidy configuration file
preceding namespace prefix
colons, double (::)
referencing static methods
referencing static properties
COM extension, changes to
command-line version of PHP
$argc variable for
$argi variable for
$argn variable for
$argv variable for
individual line processing
Comment node type
comment nodes, DOM
<!-- -->, comments in XML
comments about this book
comments, XML
COMMIT keyword
MySQL
SQLite
compatibility mode for cloning objects
compress.bzip2 wrapper 2nd
compress.zlib wrapper 2nd
compression wrappers
configuration directives [See also PHP 5, configuring]
$argc and $argv variables
automatically prepended file
caching WSDL
cloning objects instead of using a reference
error settings
ftp wrapper
HTTP POST data
http wrapper 2nd
https wrapper
MySQL
new for PHP 5
Console_Getopt package
constants, for final properties
__construct( ) method 2nd 3rd
constructors 2nd 3rd
defining
examples of 2nd
exceptions thrown by
contact information about this book
conventions used in this book
convert.iconv.* filter 2nd
$_COOKIES variable
cookies, adding to HTTP request
create_attribute( ) method
create_cdata_section( ) method
create_comment( ) method 2nd
create_element( ) method 2nd 3rd
create_processing_instruction( ) method
create_text_node( ) method
createAttribute( ) method
createCDATASection( ) method
createComment( ) method 2nd
createDocumentFragment( ) method
createElement( ) method 2nd
createProcessingInstruction( ) method
createTextNode( ) method
cURL extension
current( ) method, Iterator interface

< Day Day Up >


< Day Day Up >

[ SYMBOL] [ A ] [ B ] [ C ] [ D] [ E] [ F] [ G ] [ H] [ I ] [ J] [ K ] [ L] [ M] [ N] [ O ] [ P ] [ Q ] [ R ] [ S ] [ T] [ U] [ V ] [ W] [ X ] [ Z ]

data encapsulation
data retrieval [See also queries]
MySQL 2nd
multiple queries
output parameters for
SQLite 2nd
example of
object iterators for
data types, SQLite
database [See also queries]
connecting to, static properties for
MySQL
backing up before migration
connecting to
connecting to, with SSL
handle required to access
inserting data 2nd
retrieving data 2nd 3rd 4th
transactions
SQLite
accessing
creating 2nd
indexes 2nd
inserting data 2nd 3rd
primary keys
retrieving data 2nd 3rd 4th
schema for, example of
transactions
database abstraction layer
debug_backtrace( ) function 2nd
debug_print_backtrace( ) function 2nd
debugging 2nd 3rd
declaration, XML
deep clone/copy
__destruct( ) method 2nd
destructors 2nd 3rd
die( ) function
dir_closedir( ) method, for wrappers
dir_opendir( ) method, for wrappers
dir_readdir( ) method, for wrappers
dir_rewinddir( ) method, for wrappers
directories
enabling PHP 5 on directory-by-directory basis 2nd
reading files from 2nd
reading files from recursively
DIRECTORY_SEPARATOR constant
DirectoryIterator class 2nd 3rd 4th
DirectoryTreeIterator class
--disable-libxml flag
--disable-dom flag
--disable-simplexml flag
--disable-xml flag
--disable-overload flag
--disable-spl flag
DocBook Document node type
Document Fragment node type
Document node type
Document Object Model [See DOM extension]
Document Type Definition (DTD) 2nd
Document Type node type
documentElement property
dollar sign ($), preceding property name
DOM extension 2nd 3rd
catching exceptions in
configuration for
converting DOM objects to SimpleXML objects
converting DOM objects to XML 2nd
converting SimpleXML objects to DOM
creating custom class when reading XML
creating XML documents 2nd 3rd
exceptions thrown by
migrating to PHP 5
nodes containing XML
attribute nodes
comment nodes
element nodes 2nd
root node
text nodes 2nd
traversing
types of
reading namespaced elements 2nd
reading XML and searching with XPath 2nd
reading XML into DOM objects
reading XML with
transforming XML with XSLT
validating XML against a schema
whitespace, ignoring
dom_import_simplexml( ) function
DOMAttr class 2nd
DOMComment class
DOMDocument class
DOMElement class 2nd 3rd 4th
DOMException class 2nd
DOMText class 2nd
DOMXPath class
double at signs (@@), preceding MySQL-specific variable
double colons (::)
referencing static methods
referencing static properties
double quotes (")
around attributes in XML
automatically inserted for string parameters
escaping in queries 2nd 3rd
double slashes (//), in HTTP request
downloading PHP 5
DTD (Document Type Definition) 2nd
DTD node type
DuBois, Paul (MySQL Cookbook)
dynamic iterable objects

< Day Day Up >


< Day Day Up >

[ SYMBOL] [ A ] [ B ] [ C ] [ D] [ E ] [ F] [ G ] [ H] [ I ] [ J] [ K ] [ L] [ M] [ N] [ O ] [ P ] [ Q ] [ R ] [ S ] [ T] [ U] [ V ] [ W] [ X ] [ Z ]

-E flag, PHP command line


E_STRICT error setting 2nd
Element Declaration node type
Element node type
element nodes, DOM 2nd
elements, XML 2nd 3rd
empty element in XML
--enable-shmop flag
--enable-soap flag 2nd
--enable-sqlite-utf8 flag
--enable-force-cgi-redirect flag
encapsulation 2nd
encryption of database connections 2nd
entities, XML
Entity Declaration node type
Entity node type
Entity Reference node type
equality, strict equality operator (===)
error handling 2nd
backtraces 2nd 3rd 4th
exceptions 2nd
benefits of
catching 2nd
circumstances causing
constructors throwing
custom handler for
defining
determining information about
embedding custom data in
SOAP faults converted to
throwing
user-thrown exceptions
when to use
MySQL
SQLite 2nd
types of errors
error log, redirecting backtraces to
error, standard (stderr)
error_log( ) function
error_reporting configuration directive
escapeString( ) method
Essential PHP Tools: Modules, Extensions, and Accelerators (Sklar)
Essential PHP Tools\: Modules, Extensions, and Accelerators (Sklar)
examples [See address book application example code examples]
Exception class 2nd
backtrace access by
extending
exceptions 2nd
benefits of
catching 2nd
circumstances causing
constructors throwing
custom handler for
defining
determining information about
embedding custom data in
SOAP faults converted to
throwing
user-thrown exceptions
when to use
execute( ) method 2nd
expat library
export( ) method, Reflection class
extends keyword
Extensible Stylesheet Language Transformations (XSLT) [See also XSLT extension] 2nd
extensions, XML, list of

< Day Day Up >


< Day Day Up >

[ SYMBOL] [ A ] [ B ] [ C ] [ D] [ E] [ F] [ G ] [ H] [ I ] [ J] [ K ] [ L] [ M] [ N] [ O ] [ P ] [ Q ] [ R ] [ S ] [ T] [ U] [ V ] [ W] [ X ] [ Z ]

fetch methods, alternative


fetch( ) method 2nd 3rd
fetchSingle( ) method
file wrapper 2nd
file_get_contents( ) function 2nd
reading from standard input
stream functions called by
file_put_contents( ) function 2nd 3rd
integrating filters with
stream functions called by
writing to standard error
writing to standard output
$_FILES variable
files [See also HTML; XML]
compressing
deleting
reading from a directory 2nd
reading from a directory recursively
reading with streams
redirecting backtraces to
writing using streams
filter( ) method 2nd 3rd
FilterIterator class 2nd 3rd
filters 2nd 3rd
chaining 2nd
chaining iterators acting as
converting character sets
creating
decoding HTML entities
determining available filters
encoding HTML entities
integrating with functions that can't append filters
php://filter wrapper 2nd
provided with PHP 2nd
registering
removing HTML tags from data
final classes
final keyword
final methods 2nd
final properties
finally block
first_child( ) method 2nd
firstChild property 2nd
fonts used in this book
fopen( ) function
foreach loops 2nd 3rd 4th
formatOutput attribute
Friedl, Jeffrey E. F. (Mastering Regular Expressions)
ftok( ) function
ftp wrapper 2nd
ftps wrapper 2nd
function( ) method 2nd
functions [See also methods; specific functions]
aggregate, subselects and
calling from stylesheets
changes in mysqli extension
exceptions not thrown by
modified in mysqli extension
MySQL compared to SQLite
namespaces for, PHP not supporting
renamed for oci8 extension
replaced in mysqli extension
spaces after, ignoring 2nd
user-defined, in SQLite
functionString( ) method 2nd 3rd
fwrite( ) function

< Day Day Up >


< Day Day Up >

[ SYMBOL] [ A ] [ B ] [ C ] [ D] [ E] [ F] [ G] [ H] [ I ] [ J] [ K ] [ L] [ M] [ N] [ O ] [ P ] [ Q ] [ R ] [ S ] [ T] [ U] [ V ] [ W] [ X ] [ Z ]

garbage collection, object-oriented programming


Garfinkel, Simson (Web Security, Privacy & Commerce)
__get( ) method 2nd
$_GET variable
get_class( ) method
get_elements_by_tagname( ) method
getChildren( ) method, RecursiveIterator interface
getCode( ) method, Exception class 2nd
getDepth( ) method, RecursiveIteratorIterator class
getElementsByTagname( ) method
getElementsByTagnameNS( ) method
getFile( ) method, Exception class 2nd 3rd
getFilename( ) method, DirectoryIterator class
getIterator( ) method, IteratorAggregate interface 2nd
getLine( ) method, Exception class 2nd
getMessage( ) method, Exception class 2nd 3rd
getMethods( ) method, ReflectionClass class
getName( ) method, ReflectionMethod class
getPath( ) method, DirectoryIterator class
getPathname( ) method, DirectoryIterator class
getTrace( ) method, Exception class 2nd
getTraceAsString( ) method, Exception class 2nd 3rd
GRANT keyword, SQLite not supporting
GRANT tables
altering when migrating to MySQL 4.1
requiring SSL with
Gutmans, Andi (creator of PHP 3)

< Day Day Up >


< Day Day Up >

[ SYMBOL] [ A ] [ B ] [ C ] [ D] [ E] [ F] [ G ] [ H] [ I ] [ J] [ K ] [ L] [ M] [ N] [ O ] [ P ] [ Q ] [ R ] [ S ] [ T] [ U] [ V ] [ W] [ X ] [ Z ]

handle( ) method
Harold, Elliotte Rusty (XML in a Nutshell) 2nd
hasChildren( ) method, RecursiveIterator interface
have_openssl variable 2nd
header option, http and https wrappers 2nd
High Performance MySQL (Zawodny; Balling) 2nd
HTML
cleaning into valid HTML
compared to XML
decoding HTML entities, filter for
encoding HTML entities, filter for
optimizing
removing tags from data
HTML Document node type
HTML templates, creating
html_entity_decode( ) function
htmlentities( ) function 2nd 3rd
HTTP Developer's Handbook (Shiflett)
HTTP POST, wrapper for
http wrapper 2nd
http_get_contents( ) function
$HTTP_GET_VARS input arrays
$HTTP_POST_VARS input arrays
$HTTP_RAW_POST_DATA variable
https wrapper 2nd

< Day Day Up >


< Day Day Up >

[ SYMBOL] [ A ] [ B ] [ C ] [ D] [ E] [ F] [ G ] [ H] [ I] [ J] [ K ] [ L] [ M] [ N] [ O ] [ P ] [ Q ] [ R ] [ S ] [ T] [ U] [ V ] [ W] [ X ] [ Z ]

icons used in this book


iconv command
iconv( ) function
importStylesheet( ) method
in-memory tables, SQLite
indexes, SQLite 2nd
inheritance
multiple inheritance
of properties
inner joins
InnoDB table format 2nd
input parameters, binding 2nd
input to PHP, wrappers for
input to web servers, wrappers for
input, filtering
input, standard (stdin)
INSERT statement 2nd 3rd 4th
installing PHP 5 2nd
instanceof operator 2nd
instantiation [See constructors]
interface keyword
interfaces 2nd
intval( ) function
is_file( ) function
is_soap_fault( ) method
ISAM table format
isDir( ) method, DirectoryIterator class 2nd
isDot( ) method, DirectoryIterator class
isPublic( ) method, ReflectionMethod class
item( ) method
iteration
arrays 2nd
classes
foreach loops 2nd 3rd 4th
properties 2nd
while loops 2nd
Iterator interface 2nd
IteratorAggregate interface 2nd
IteratorAggregate( ) method
iterators 2nd
aggregating into a class 2nd
chaining to filter results
creating 2nd
dynamic iterable objects
list of, in SPL
meta-iterators 2nd
query results
reading files from a directory
recursive 2nd
SQLite

< Day Day Up >


< Day Day Up >

[ SYMBOL] [ A ] [ B ] [ C ] [ D] [ E] [ F] [ G ] [ H] [ I ] [ J] [ K ] [ L] [ M] [ N] [ O ] [ P ] [ Q ] [ R ] [ S ] [ T] [ U] [ V ] [ W] [ X ] [ Z ]

joins
inner joins
self-joins

< Day Day Up >


< Day Day Up >

[ SYMBOL] [ A ] [ B ] [ C ] [ D] [ E] [ F] [ G ] [ H] [ I ] [ J] [ K] [ L] [ M] [ N] [ O ] [ P ] [ Q ] [ R ] [ S ] [ T] [ U] [ V ] [ W] [ X ] [ Z ]

key( ) method, Iterator interface


keys [See indexes, SQLite]
Kulchenko, Pavel (Programming Web Services with SOAP)

< Day Day Up >


< Day Day Up >

[ SYMBOL] [ A ] [ B ] [ C ] [ D] [ E] [ F] [ G ] [ H] [ I ] [ J] [ K ] [ L] [ M] [ N] [ O ] [ P ] [ Q ] [ R ] [ S ] [ T] [ U] [ V ] [ W] [ X ] [ Z ]

Lane, David (Web Database Applications with PHP and MySQL)


last_child( ) method
lastChild property
lastError( ) method
lastInsertRowid( ) method
Learning PHP 5 (Sklar)
Lerdorf, Rasmus
creator of PHP
Programming PHP
libxml2 library 2nd
configuration for
node types in
libxslt library
LimitIterator class 2nd 3rd 4th
line contents, individual line processing
line number, individual line processing
load( ) method
loadXML( ) method
local name, XML
local-name( ) function
log_error( ) function
loops [See iteration iterators]

< Day Day Up >


< Day Day Up >

[ SYMBOL] [ A ] [ B ] [ C ] [ D] [ E] [ F] [ G ] [ H] [ I ] [ J] [ K ] [ L] [ M] [ N] [ O ] [ P ] [ Q ] [ R ] [ S ] [ T] [ U] [ V ] [ W] [ X ] [ Z ]

mail.force_extra_parameters configuration directive


mailing lists for PHP
Mangano, Sal (XSLT Cookbook)
Mastering Regular Expressions (Friedl)
MDB
Means, W. Scott (XML in a Nutshell) 2nd
:memory: token, for SQLite database name
memory management, object-oriented programming 2nd [See also RAM]
meta-iterators 2nd
method option, http and https wrappers
method_exists( ) method
methods [See also constructors; functions; specific methods]
abstract
accessing from within a class
accessors 2nd 3rd
calling
declaring
defining
destructors 2nd 3rd
final 2nd
private 2nd 3rd
protected
public 2nd
requiring passed objects of specific class
requiring support for, with interfaces
static
undefined, calling
wrapper methods
migration
choosing method of
steps for
to MySQL 4.1 2nd 3rd 4th
to mysqli extension 2nd
to PHP 5 2nd 3rd 4th
when not to
mkdir( ) method, for wrappers
multiple inheritance
my.cnf files 2nd
MyISAM table format 2nd 3rd
MySQL 2nd [See also mysql extension; mysqli extension]
books about 2nd 3rd
configuration for
connecting to database server
downloading and installing
functions, compared to SQLite functions
multiple queries
password schema for
query iterator for
querying with prepared statements
SSL, securing connections with
subselects
table formats used by
transactions
Version 4.1
features of 2nd
migrating to 2nd 3rd 4th
versions supported by mysqli extension 2nd
MySQL Cookbook (DuBois)
mysql extension [See also MySQL]
connecting to database
enabling in addition to mysqli extension
functions modified in mysqli extension
functions replaced in mysqli extension
multiple queries
using with MySQL 4.1 2nd
MySQL Reference Manual (Widenius; Axmark)
mysql.default_host configuration directive
mysql_change_user( ) function
mysql_close( ) function
mysql_config file
mysql_connect( ) function 2nd
mysql_convert_table_format script
mysql_create_db( ) function
mysql_db_name( ) function
mysql_db_query( ) function
mysql_drop_db( ) function
mysql_errno( ) function
mysql_error( ) function
mysql_escape_string( ) function 2nd 3rd
mysql_fetch_field( ) function
mysql_fetch_row( ) function 2nd
mysql_field_flags( ) function
mysql_field_len( ) function
mysql_field_name( ) function
mysql_field_table( ) function 2nd
mysql_field_type( ) function
mysql_fix_privilege_tables script
mysql_free_result( ) function
mysql_get_host_info( ) function
mysql_get_proto_info( ) function
mysql_get_server_info( ) function
mysql_info( ) function
mysql_insert_id( ) function
mysql_list_dbs( ) function
mysql_list_fields( ) function
mysql_list_processes( ) function
mysql_list_tables( ) function
mysql_pconnect( ) function 2nd 3rd
mysql_ping( ) function
mysql_query( ) function 2nd
mysql_real_escape_string( ) function 2nd
mysql_result( ) function
mysql_select_database( ) function
mysql_select_db( ) function
mysql_stat( ) function
mysql_stmt_init( ) function
mysql_tablename( ) function
mysql_thread_id( ) function
mysql_unbuffered_query( ) function
mysqli extension 2nd [See also MySQL] 3rd
configuring 2nd
connecting to database
enabling in addition to mysql extension
exceptions not thrown by
features of
fetch methods, alternative
migrating to 2nd
multiple queries
MySQL versions supported by 2nd
object-oriented interface 2nd
procedural interface for
SSL-enabled
mysqli object
mysqli( ) constructor
mysqli_affected_rows( ) function
mysqli_change_user( ) function
mysqli_close( ) function 2nd
mysqli_connect( ) function 2nd 3rd 4th 5th 6th
mysqli_data_seek( ) function 2nd
mysqli_errno( ) function 2nd 3rd
mysqli_error( ) function 2nd
mysqli_fetch_array( ) function 2nd
mysqli_fetch_assoc( ) function
mysqli_fetch_field( ) function 2nd
mysqli_fetch_object( ) function
mysqli_fetch_row( ) function 2nd 3rd 4th
mysqli_free_result( ) function
mysqli_get_host_info( ) function
mysqli_get_proto_info( ) function
mysqli_get_server_info( ) function
mysqli_info( ) function
mysqli_init( ) function 2nd 3rd
mysqli_insert_id( ) function 2nd 3rd
mysqli_more_results( ) function
mysqli_multi_query( ) function 2nd
mysqli_next_result( ) function 2nd
mysqli_num_rows( ) function
mysqli_options( ) function
mysqli_ping( ) function
mysqli_query( ) function 2nd 3rd 4th 5th 6th 7th 8th
MYSQLI_READ_DEFAULT_FILE option 2nd
mysqli_real_connect( ) function
mysqli_real_escape_string( ) function 2nd 3rd 4th
mysqli_real_query( ) function
mysqli_select_database( ) function
mysqli_select_db( ) function 2nd
MYSQLI_SSL_CLIENT flag
mysqli_ssl_set( ) function
mysqli_stat( ) function
mysqli_stmt object
mysqli_stmt_bind_param( ) function 2nd 3rd
mysqli_stmt_bind_result( ) function 2nd
mysqli_stmt_execute( ) function 2nd
mysqli_stmt_fetch( ) function 2nd
mysqli_stmt_init( ) function
mysqli_stmt_prepare( ) function
mysqli_store_result( ) function 2nd
mysqli_thread_id( ) function
mysqli_use_db( ) function
mysqli_use_result( ) function
MySQLiQueryIterator class 2nd

< Day Day Up >


< Day Day Up >

[ SYMBOL] [ A ] [ B ] [ C ] [ D] [ E] [ F] [ G ] [ H] [ I ] [ J] [ K ] [ L] [ M] [ N] [ O ] [ P ] [ Q ] [ R ] [ S ] [ T] [ U] [ V ] [ W] [ X ] [ Z ]

Namespace Declaration node type


namespace-uri( ) function
namespaces for functions and objects, PHP not supporting
namespaces, XML 2nd
books about
default namespaces
defining
prefixes for
reading namespaced elements
reading namespaced elements and searching with XPath
new keyword
next( ) method, Iterator interface
next_sibling( ) method
nextSibling property
node_value( ) method
nodeType method
nodeValue attribute
nodeValue property
Notation node type

< Day Day Up >


< Day Day Up >

[ SYMBOL] [ A ] [ B ] [ C ] [ D] [ E] [ F] [ G ] [ H] [ I ] [ J] [ K ] [ L] [ M] [ N] [ O] [ P ] [ Q ] [ R ] [ S ] [ T] [ U] [ V ] [ W] [ X ] [ Z ]

object-oriented programming
autoload feature
binding parameters 2nd 3rd
data encapsulation
garbage collection
inheritance
memory management
mysqli extension
objects
prepared statements using
retrieving information about classes [See Reflection classes]
SQLite interface 2nd
Tidy interface
objects [See also specific classes or objects] 2nd 3rd
casting to integer or floating-point number
cloning 2nd
converting to strings
destructors for 2nd 3rd
determining class from
dynamic iterable objects
instantiating 2nd 3rd [See also constructors]4th
methods of, calling
namespaces for, PHP not supporting
passing an object handle
passing by reference
passing by value 2nd
properties of [See properties]
referencing
subclassing
oci8 extension, functions renamed for
--old-passwords flag 2nd
old_function, not supported in PHP 5
onClose( ) method, for filters 2nd
onCreate( ) method, for filters 2nd
OOP [See object-oriented programming]
opendir( ) function
OpenSSL
adding to MySQL and PHP
downloading and installing
Oracle extension, functions renamed for
output from PHP, wrappers for
output from web servers, wrappers for
output parameters, binding
output, filtering
output, standard (stdout)
Overload extension
< Day Day Up >
< Day Day Up >

[ SYMBOL] [ A ] [ B ] [ C ] [ D] [ E] [ F] [ G ] [ H] [ I ] [ J] [ K ] [ L] [ M] [ N] [ O ] [ P] [ Q ] [ R ] [ S ] [ T] [ U] [ V ] [ W] [ X ] [ Z ]

parameters
input, binding 2nd
optional, passing by reference
output, binding 2nd
parent class
parent prefix
parent_node( ) method
ParentIterator class
parentNode property
parse_url( ) function
pass-by-reference
pass-by-value 2nd 3rd
passing an object handle
password
database class constructor
FTP 2nd
HTTP Basic Authentication credentials
MySQL database 2nd 3rd 4th
PATH_TRANSLATED setting for Apache
PEAR (PHP Extension and Application Repository)
Console_Getopt package
PEAR DB
SOAP classes
web site
XML_DTD package
PEAR-style naming convention
PECL (PHP Extension Community Library) web site
performance
CGI version of PHP
conversion between SimpleXML and DOM objects
MySQL 2nd
PHP 2nd
SQLite 2nd
permission to use code examples
Person class, address book application
PHP 3
PHP 4
DOM extension 2nd 3rd
creating XML
XPath and 2nd
error handling
history of
iteration
limitations of
query method used by
recursive iteration
running with PHP 5 simultaneously 2nd
SOAP support
XSLT extension
PHP 5
books about 2nd
CGI version of 2nd
configuring [See also configuration directives] 2nd
downloading 2nd
enabling on another port 2nd
enabling on directory-by-directory basis 2nd
example application (see address book application example
features of 2nd
history of
installing 2nd
migrating to 2nd 3rd 4th
namespaces for functions and objects not supported
OO features for
running with PHP 4 simultaneously 2nd
version used for this book
XML extensions, list of
PHP command line
PHP Cookbook (Sklar; Trachtenberg)
PHP DevCenter web site
PHP Extension and Application Repository [See PEAR]
PHP Extension Community Library (PECL)
PHP mailing lists web site
PHP Manual
PHP Presentation archive web site
php wrapper 2nd
PHP/FI
php://filter wrapper 2nd
php://input wrapper
php://output wrapper
php://stderr wrapper
php://stdin wrapper
php://stdout wrapper
php:function( ) function
php_user_filter class
PHPCommunity.org web site
PHPDocumentor
phpMyAdmin
PIs [See processing instructions]
ports, enabling PHP 5 on specific port 2nd
$_POST variable
POST, wrapper for
--prefix=/usr/local/php5 flag
prepare( ) method 2nd
prepared statements, MySQL
preserveWhiteSpace attribute 2nd
previous_sibling( ) method
previousSibling property
primary keys, SQLite
private keyword
private methods 2nd 3rd
private properties 2nd
procedures [See functions]
<? ?\\>, processing instructions in XML
processing instructions (PIs)
creating
node type for
SimpleXML not supporting
programming errors
Programming PHP (Lerdorf; Tatroe)
Programming Web Services with SOAP (Snell; Tidwell; Kulchenko)
properties
defining
encapsulating access to
final
inheritance and
iterating 2nd
predeclaring
private 2nd
public 2nd
static
protected keyword
protected methods
proxy option, http and https wrappers
public keyword 2nd
public methods 2nd
public properties 2nd

< Day Day Up >


< Day Day Up >

[ SYMBOL] [ A ] [ B ] [ C ] [ D] [ E] [ F] [ G ] [ H] [ I ] [ J] [ K ] [ L] [ M] [ N] [ O ] [ P ] [ Q] [ R ] [ S ] [ T] [ U] [ V ] [ W] [ X ] [ Z ]

qualified name, XML


queries [See also data retrieval]
iterator for
MySQL 2nd
binding parameters for
multiple 2nd
number of rows returned by, configuring
prepared statements for
subselects
transactions for
SQLite 2nd 3rd 4th
query( ) method 2nd 3rd
question mark (?), substituting for query variables
quotes
around attributes in XML
automatically escaped by prepared statements
automatically inserted for string parameters
escaped with mysqli_real_escape_string( ) function
escaping with sqlite_escape_string( ) function

< Day Day Up >


< Day Day Up >

[ SYMBOL] [ A ] [ B ] [ C ] [ D] [ E] [ F] [ G ] [ H] [ I ] [ J] [ K ] [ L] [ M] [ N] [ O ] [ P ] [ Q ] [ R ] [ S ] [ T] [ U] [ V ] [ W] [ X ] [ Z ]

-R flag, PHP command line


RAM
garbage collection and
in-memory tables
multiple MySQL queries and
wrapper for shared memory functions
readdir( ) function
recursive iterators 2nd
RecursiveDirectoryIterator class 2nd 3rd
RecursiveIterator interface 2nd
RecursiveIteratorIterator class 2nd
reference-counting method of garbage collection
Reflection classes 2nd 3rd
exporting overview information about classes
inspecting classes
inspecting methods
inspecting properties
methods for, list of
ReflectionClass class 2nd
ReflectionExtension class
ReflectionFunction class
ReflectionMethod class 2nd
ReflectionParameter class 2nd
ReflectionProperty class 2nd
register_argc_argv configuration setting
register_long_arrays configuration directive
register_shutdown_function( ) method
registerNamespace( ) method
registerPHPFunctions( ) method
regular expressions
book about
iterator using
RelaxNG
RelaxNG schema, validating XML against
relaxNGValidate( ) method
relaxNGValidateSource( ) method
request_fulluri option, http and https wrappers
resource failures
resources [See books web sites]
REST
REVOKE keyword, SQLite not supporting
rewind( ) method, Iterator interface
rmdir( ) method, for wrappers
ROLLBACK keyword
MySQL
SQLite
root element, XML
root node, DOM

< Day Day Up >


< Day Day Up >

[ SYMBOL] [ A ] [ B ] [ C ] [ D] [ E] [ F] [ G ] [ H] [ I ] [ J] [ K ] [ L] [ M] [ N] [ O ] [ P ] [ Q ] [ R ] [ S] [ T] [ U] [ V ] [ W] [ X ] [ Z ]

save( ) method
saveXML( ) method
SAX extension 2nd 3rd 4th
schemas
database
formats for
validating XML against
schemaValidate( ) method 2nd
schemaValidateSource( ) method 2nd
scheme handlers
Schlossnagle, George (Advanced PHP Programming)
security [See also password; SSL]
book about
SeekableIterator interface
SELECT statement [See data retrieval queries]
self prefix 2nd
self-joins
sendmail program, passing extra parameters to
session.hash_bits_per_character configuration directive
session.hash_function configuration directive
__set( ) method 2nd
set_error_handler( ) function
set_exception_handler( ) function
setClass( ) method
setParameter( ) method
shallow clone/copy
shared memory functions, wrapper for
Shiflett, Chris (HTTP Developer's Handbook)
shmop extension
SimpleXML extension 2nd 3rd
configuration for
converting DOM objects to SimpleXML objects
converting SimpleXML objects to DOM
converting to XML
reading namespaced elements 2nd
reading XML and searching with XPath 2nd
reading XML documents
reading XML into SimpleXML objects
streams used by
simplexml_import_dom( ) function
simplexml_load_file( ) function 2nd
simplexml_load_string( ) function
SimpleXMLIterator class 2nd 3rd
Simpson, John E.
"Top Ten Tips to Using XPath and XPointer"
XPath and XPointer
single quotes (')
around attributes in XML
automatically inserted for string parameters
escaping in queries 2nd 3rd
Sklar, David
Essential PHP Tools: Modules, Extensions, and Accelerators 2nd
Learning PHP 5
PHP Cookbook
slash (/)
empty element in XML
separating levels in XML
slashes, double (//), in HTTP request
Snell, James (Programming Web Services with SOAP)
SOAP extension 2nd 3rd
caching WSDL file
configuration for
enabling
faults generated by 2nd
PEAR SOAP classes
processing requests with SOAP server
querying a server 2nd
version of SOAP supported
WSDL not generated by
soap.wsdl_cache_dir configuration directive
soap.wsdl_cache_enabled configuration directive
soap.wsdl_cache_ttl configuration directive
SoapClient class
SoapServer class
Spafford, Gene (Web Security, Privacy & Commerce)
SPL (Standard PHP Library) 2nd
classes and interfaces, list of
configuration for
SQLite 2nd
accessing databases
binary data and
configuration for
creating databases 2nd
creating tables
data retrieval 2nd 3rd 4th
data types in
error handling
exceptions not thrown by
functions, compared to MySQL functions
in-memory tables
indexes 2nd
inserting data in database 2nd 3rd
limitations of
object-oriented interface 2nd
performance of
primary keys
schema for, example of
transactions
user-defined functions (UDFs)
when to use
sqlite.assoc_case parameter
sqlite_array_query( ) function
SQLITE_ASSOC parameter
SQLITE_BOTH parameter
sqlite_close( ) function 2nd 3rd
sqlite_connect( ) function
sqlite_create_aggregate( ) function
sqlite_create_function( ) function
sqlite_error_string( ) function
sqlite_escape_string( ) function 2nd 3rd
sqlite_fetch_array( ) function 2nd 3rd 4th 5th
sqlite_fetch_single( ) function
sqlite_last_error( ) function 2nd
sqlite_last_insert_rowid( ) function 2nd
SQLITE_NUM parameter
sqlite_num_rows( ) function 2nd
sqlite_open( ) function 2nd
sqlite_query( ) function 2nd 3rd 4th 5th
sqlite_query_array( ) function
sqlite_query_unbuffered( ) function
sqlite_udf_binary_decode( ) function
sqlite_udf_binary_encode( ) function
sqlite_unbuffered_query( ) function
SQLiteDatabase class
SQLiteException 2nd
SSL
connecting to databases using 2nd
ftps wrapper and 2nd
https wrapper and
ssl-capath option
ssl-ciphers option
standard error (stderr)
standard input (stdin)
standard output (stdout)
Standard PHP Library [See SPL]
standard UDFs 2nd
static keyword
static methods
static properties
stderr (standard error)
stdin (standard input)
stdout (standard output)
stmt_init( ) method 2nd
stream_close( ) method, for wrappers 2nd 3rd
stream_context_create( ) function 2nd
stream_context_get_options( ) method, for wrappers
stream_eof( ) method, for wrappers 2nd
stream_filter_append( ) function 2nd
stream_filter_prepend( ) function
stream_filter_register( ) function
stream_flush( ) method, for wrappers
stream_get_filters( ) function
stream_get_wrappers( ) function
stream_open( ) method, for wrappers 2nd 3rd
stream_read( ) method, for wrappers 2nd
stream_seek( ) method, for wrappers 2nd
stream_stat( ) method, for wrappers
stream_tell( ) method, for wrappers 2nd
stream_wrapper_register( ) method 2nd
stream_write( ) method, for wrappers 2nd
streams 2nd
contexts for
extending [See wrappers]
modifying data in [See filters]
nonlocal files accessed by
reading files with
SimpleXML using
writing to files using
strict equality operator (===)
string stream_read( ) method, for wrappers
string.rot13 filter
string.strip_tags filter 2nd
string.tolower filter
string.toupper filter
strings
converting objects to
filters for
functions for
treating as arrays
strpos( ) function
strripos( ) function
strrpos( ) function
strtr( ) function
stylesheets [See XSLT XSLT extension]
subclassing objects
subselects
super class
superglobal variables
Suraski, Zeev (creator of PHP 3)

< Day Day Up >


< Day Day Up >

[ SYMBOL] [ A ] [ B ] [ C ] [ D] [ E] [ F] [ G ] [ H] [ I ] [ J] [ K ] [ L] [ M] [ N] [ O ] [ P ] [ Q ] [ R ] [ S ] [ T ] [ U] [ V ] [ W] [ X ] [ Z ]

tables
aliases for
converting format of
creating in SQLite
in-memory, SQLite
InnoDB format for 2nd
ISAM format for
MyISAM format for 2nd 3rd
tagName property
Tatroe, Kevin (Programming PHP)
Template class, address book application
templates
displaying output using
HTML
plain-text
Text node type
text nodes, DOM 2nd
The Annotated PHP Manual web site
$this variable 2nd 3rd
throw keyword
Tidwell, Doug
Programming Web Services with SOAP
XSLT
Tidy extension 2nd 3rd
cleaning HTML and XML files
configuring 2nd
enabling
object-oriented interface for
optimizing files
tidy_clean_repair( ) function
tidy_get_output( ) function
tidy_parse_file( ) function
tidy_parse_string( ) function
"Top Ten Tips to Using XPath and XPointer" (Simpson)
__toString( ) method 2nd 3rd
Trachtenberg, Adam (PHP Cookbook)
transactions
MySQL
SQLite
transformations, XML
transformToDoc( ) method
transformToURI( ) method
transformToXML( ) method
try block
type hints
typographical conventions used in this book
< Day Day Up >
< Day Day Up >

[ SYMBOL] [ A ] [ B ] [ C ] [ D] [ E] [ F] [ G ] [ H] [ I ] [ J] [ K ] [ L] [ M] [ N] [ O ] [ P ] [ Q ] [ R ] [ S ] [ T] [ U] [ V ] [ W] [ X ] [ Z ]

UDFs (user-defined functions), SQLite


unbufferedQuery( ) method 2nd
unlink( ) method, for wrappers 2nd
unset( ) method 2nd
Upgrading/Downgrading MySQL, MySQL Manual
url_stat( ) method, for wrappers
user-defined functions (UDFs), SQLite
user_agent configuration directive
user_agent option, http and https wrappers

< Day Day Up >


< Day Day Up >

[ SYMBOL] [ A ] [ B ] [ C ] [ D] [ E] [ F] [ G ] [ H] [ I ] [ J] [ K ] [ L] [ M] [ N] [ O ] [ P ] [ Q ] [ R ] [ S ] [ T] [ U] [ V] [ W] [ X ] [ Z ]

valid( ) method, Iterator interface


validate( ) method
var keyword
variables
passing by reference
passing by value
superglobal
variables_order configuration directive
verify_peer option, https wrapper
void stream_close( ) method, for wrappers

< Day Day Up >


< Day Day Up >

[ SYMBOL] [ A ] [ B ] [ C ] [ D] [ E] [ F] [ G ] [ H] [ I ] [ J] [ K ] [ L] [ M] [ N] [ O ] [ P ] [ Q ] [ R ] [ S ] [ T] [ U] [ V ] [ W] [ X ] [ Z ]

Web Database Applications with PHP and MySQL (Williams; Lane)


Web Security, Privacy & Commerce (Garfinkel; Spafford)
web servers [See also Apache; http wrapper; https wrapper]
input and output from, wrappers for
running two servers while migrating 2nd
web services 2nd [See also REST; SOAP extension]
passing multiple parameters to
querying a server
WSDL file defining
Web Services Description Language (WSDL)
querying a server using
SOAP caching WSDL file
Web Services Essentials (Cerami)
web sites
for this book
libxml2 library
MySQL manual, "Upgrading/Downgrading MySQL"
MySQLi classes, methods, and properties
mysqli_options( ) options
OpenSSL downloads
PHP
PHP downloads 2nd
PHP Manual
PHPDocumentor
phpMyAdmin
RelaxNG
REST
SOAP
SPL section, PHP manual
SQLite
SSL certificates
stream_wrapper_register( ) documentation
Streams section, PHP Manual
Tidy configuration options
WSDL
XMethods
XML address book schemas
XSLT specification
well-formed XML
while loops 2nd
whitespace, DOM ignoring
Widenius, Michael "Monty" (MySQL Reference Manual)
wildcards, SQL, filtering
Williams, Hugh E. (Web Database Applications with PHP and MySQL)
Windows
integrating PHP 5 into Apache
Windows 95 not supported by PHP 5
--with-mysqli flag
--with-vio flag
--with-openssl flag
--with-isam flag
--with-libxml-dir flag
--with-xsl flag 2nd
--with-openssl flag
--with-zlib flag
--with-bz2 flag
--with-openssl flag
--with-zlib flag
--with-bzip2 flag
--with-tidy flag
--with-libxml-dir flag
--with-dom-exslt flag
--with-dom-xslt flag
--with-dom flag
--with-xsl flag
--with-libexpat-dir flag
--with-libxml-dir flag
--with-mysql flag
--with-mysqli flag
--with-tidy flag
--with-apxs flag
--without-innodb flag
--without-sqlite flag
wrapper methods
wrappers 2nd 3rd
compression wrappers 2nd
creating
determining available wrappers
documentation for
file wrapper 2nd
ftp wrapper 2nd
ftps wrapper 2nd
http wrapper 2nd
https wrapper 2nd
methods for, list of
opening and closing streams
passing context values to
php wrapper 2nd
provided with PHP 5 2nd
reading and writing to streams
registering a class as
WSDL (Web Services Description Language)
querying a server using
SOAP caching WSDL file

< Day Day Up >


< Day Day Up >

[ SYMBOL] [ A ] [ B ] [ C ] [ D] [ E] [ F] [ G ] [ H] [ I ] [ J] [ K ] [ L] [ M] [ N] [ O ] [ P ] [ Q ] [ R ] [ S ] [ T] [ U] [ V ] [ W] [ X] [ Z ]

XInclude End node type


XInclude Start node type
XMethods web site
XML [See also specific extensions] 2nd 3rd
books about 2nd
CDATA (Character Data) 2nd 3rd
cleaning into valid XML
compared to HTML
configuration for 2nd
converting DOM objects to 2nd 3rd
converting SimpleXML objects to
creating XML documents with DOM 2nd 3rd
declarations
DOM extension
extensions for, list of
installing support for
namespaces
optimizing
processing instructions (PIs)
reading into DOM objects
reading into SimpleXML objects
reading namespaced elements 2nd
reading with DOM
reading with DOM and searching with XPath 2nd
reading with SimpleXML
reading with SimpleXML and searching with XPath 2nd
schemas
searching with XPath
SimpleXML extension
transformations
transforming with XSLT
validating against a schema
well-formed
XSLT extension
XML in a Nutshell (Harold; Means) 2nd
XML Namespaces
XML Schema 2nd
XML_DTD package
xml_parser_create_ns( ) function
xml_set_default_handler( ) function
xml_set_external_entity_ref_handler( ) function
xml2-config --version command
xmlns attribute 2nd
XPath
XPath and XPointer (Simpson)
XPath extension
migrating to PHP 5
searching XML with
xpath( ) method
xpath_eval( ) method
xpath_new_context( ) function 2nd
xpath_register_ns( ) function
XSLT
XSLT (Tidwell)
XSLT Cookbook (Mangano)
XSLT extension 2nd
calling PHP functions from stylesheets
configuration for
installing support for
migrating to PHP 5
passing PHP parameters to stylesheets
transforming XML with
xslt-config --version command
xslt_process( ) function
XSLTProcessor class

< Day Day Up >


< Day Day Up >

[ SYMBOL] [ A ] [ B ] [ C ] [ D] [ E] [ F] [ G ] [ H] [ I ] [ J] [ K ] [ L] [ M] [ N] [ O ] [ P ] [ Q ] [ R ] [ S ] [ T] [ U] [ V ] [ W] [ X ] [ Z ]

Zawodny, Jeremy D. (High Performance MySQL) 2nd


Zend Engine 2
zend.zel_compatibility_mode configuration directive

< Day Day Up >

You might also like