<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/css" href="http://commons.oreilly.com/wiki/skins/common/feed.css?97"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
		<id>http://commons.oreilly.com/wiki/index.php?title=PHP_Cookbook/Web_Basics&amp;action=history&amp;feed=atom</id>
		<title>PHP Cookbook/Web Basics - Revision history</title>
		<link rel="self" type="application/atom+xml" href="http://commons.oreilly.com/wiki/index.php?title=PHP_Cookbook/Web_Basics&amp;action=history&amp;feed=atom"/>
		<link rel="alternate" type="text/html" href="http://commons.oreilly.com/wiki/index.php?title=PHP_Cookbook/Web_Basics&amp;action=history"/>
		<updated>2013-05-24T11:37:49Z</updated>
		<subtitle>Revision history for this page on the wiki</subtitle>
		<generator>MediaWiki 1.11.0</generator>

	<entry>
		<id>http://commons.oreilly.com/wiki/index.php?title=PHP_Cookbook/Web_Basics&amp;diff=7320&amp;oldid=prev</id>
		<title>Docbook2Wiki: Initial conversion from Docbook</title>
		<link rel="alternate" type="text/html" href="http://commons.oreilly.com/wiki/index.php?title=PHP_Cookbook/Web_Basics&amp;diff=7320&amp;oldid=prev"/>
				<updated>2008-03-07T13:36:04Z</updated>
		
		<summary type="html">&lt;p&gt;Initial conversion from Docbook&lt;/p&gt;

			&lt;table style=&quot;background-color: white; color:black;&quot;&gt;
			&lt;col class='diff-marker' /&gt;
			&lt;col class='diff-content' /&gt;
			&lt;col class='diff-marker' /&gt;
			&lt;col class='diff-content' /&gt;
			&lt;tr&gt;
				&lt;td colspan='2' style=&quot;background-color: white; color:black;&quot;&gt;←Older revision&lt;/td&gt;
				&lt;td colspan='2' style=&quot;background-color: white; color:black;&quot;&gt;Revision as of 13:36, 7 March 2008&lt;/td&gt;
			&lt;/tr&gt;
		&lt;/table&gt;</summary>
		<author><name>Docbook2Wiki</name></author>	</entry>

	<entry>
		<id>http://commons.oreilly.com/wiki/index.php?title=PHP_Cookbook/Web_Basics&amp;diff=3119&amp;oldid=prev</id>
		<title>Evanlenz: 1 revision(s)</title>
		<link rel="alternate" type="text/html" href="http://commons.oreilly.com/wiki/index.php?title=PHP_Cookbook/Web_Basics&amp;diff=3119&amp;oldid=prev"/>
				<updated>2008-03-06T22:30:04Z</updated>
		
		<summary type="html">&lt;p&gt;1 revision(s)&lt;/p&gt;

			&lt;table style=&quot;background-color: white; color:black;&quot;&gt;
			&lt;col class='diff-marker' /&gt;
			&lt;col class='diff-content' /&gt;
			&lt;col class='diff-marker' /&gt;
			&lt;col class='diff-content' /&gt;
			&lt;tr&gt;
				&lt;td colspan='2' style=&quot;background-color: white; color:black;&quot;&gt;←Older revision&lt;/td&gt;
				&lt;td colspan='2' style=&quot;background-color: white; color:black;&quot;&gt;Revision as of 22:30, 6 March 2008&lt;/td&gt;
			&lt;/tr&gt;
		&lt;/table&gt;</summary>
		<author><name>Evanlenz</name></author>	</entry>

	<entry>
		<id>http://commons.oreilly.com/wiki/index.php?title=PHP_Cookbook/Web_Basics&amp;diff=3118&amp;oldid=prev</id>
		<title>Docbook2Wiki: Initial conversion from Docbook</title>
		<link rel="alternate" type="text/html" href="http://commons.oreilly.com/wiki/index.php?title=PHP_Cookbook/Web_Basics&amp;diff=3118&amp;oldid=prev"/>
				<updated>2008-03-06T22:28:45Z</updated>
		
		<summary type="html">&lt;p&gt;Initial conversion from Docbook&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;{{PHP Cookbook/TOC}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Web programming is probably why you're reading this book. It's why the first version of PHP was written and what continues to make it so popular today. With PHP, it's easy to write dynamic web programs that do almost anything. Other chapters cover various PHP capabilities, like graphics, regular expressions, database access, and file I/O. These capabilities are all part of web programming, but this chapter focuses on some web-specific concepts and organizational topics that will make your web programming stronger.&lt;br /&gt;
&lt;br /&gt;
[[PHP Cookbook/Web Basics#Setting Cookies|Recipe 8.2]], [[PHP Cookbook/Web Basics#Reading Cookie Values|Recipe 8.3]], and [[PHP Cookbook/Web Basics#Deleting Cookies|Recipe 8.4]] show how to set, read, and delete cookies. A cookie is a small text string that the server instructs the browser to send along with requests the browser makes. Normally, HTTP requests aren't &amp;quot;stateful&amp;quot;; each request can't be connected to a previous one. A cookie, however, can link different requests by the same user. This makes it easier to build features such as shopping carts or to keep track of a user's search history.&lt;br /&gt;
&lt;br /&gt;
[[PHP Cookbook/Web Basics#Redirecting to a Different Location|Recipe 8.5]] shows how to redirect users to a different web page than the one they requested. [[PHP Cookbook/Web Basics#Using Session Tracking|Recipe 8.6]] explains the session module, which lets you easily associate persistent data with a user as he moves through your site. [[PHP Cookbook/Web Basics#Storing Sessions in a Database|Recipe 8.7]] demonstrates how to store session information in a database, which increases the scalability and flexibility of your web site. Discovering the features of a user's browser is shown in [[PHP Cookbook/Web Basics#Detecting Different Browsers|Recipe 8.8]]. [[PHP Cookbook/Web Basics#Building a GET Query String|Recipe 8.9]] shows the details of constructing a URL that includes a GET query string, including proper encoding of special characters and handling of HTML entities.&lt;br /&gt;
&lt;br /&gt;
The next two recipes demonstrate how to use authentication, which lets you protect your web pages with passwords. PHP's special features for dealing with HTTP Basic authentication are explained in [[PHP Cookbook/Web Basics#Using HTTP Basic Authentication|Recipe 8.10]]. Sometimes it's a better idea to roll your own authentication method using cookies, as shown in [[PHP Cookbook/Web Basics#Using Cookie Authentication|Recipe 8.11]].&lt;br /&gt;
&lt;br /&gt;
The three following recipes deal with output control. [[PHP Cookbook/Web Basics#Flushing Output to the Browser|Recipe 8.12]] shows how to force output to be sent to the browser. [[PHP Cookbook/Web Basics#Buffering Output to the Browser|Recipe 8.13]] explains the output buffering functions. Output buffers enable you to capture output that would otherwise be printed or delay output until an entire page is processed. Automatic compression of output is shown in [[PHP Cookbook/Web Basics#Compressing Web Output with gzip|Recipe 8.14]].&lt;br /&gt;
&lt;br /&gt;
[[PHP Cookbook/Web Basics#Hiding Error Messages from Users|Recipe 8.15]] to [[PHP Cookbook/Web Basics#Logging Debugging Information|Recipe 8.20]] cover error handling topics, including controlling where errors are printed, writing custom functions to handle error processing, and adding debugging assistance information to your programs. [[PHP Cookbook/Web Basics#Eliminating &amp;quot;headers already sent&amp;quot; Errors|Recipe 8.19]] includes strategies for avoiding the common &amp;quot;headers already sent&amp;quot; error message, such as using the output buffering discussed in [[PHP Cookbook/Web Basics#Buffering Output to the Browser|Recipe 8.13]].&lt;br /&gt;
&lt;br /&gt;
The next four recipes show how to interact with external variables: environment variables and PHP configuration settings. [[PHP Cookbook/Web Basics#Reading Environment Variables|Recipe 8.21]] and [[PHP Cookbook/Web Basics#Setting Environment Variables|Recipe 8.22]] discuss environment variables, while [[PHP Cookbook/Web Basics#Reading Configuration Variables|Recipe 8.23]] and [[PHP Cookbook/Web Basics#Setting Configuration Variables|Recipe 8.24]] discuss reading and changing PHP configuration settings. If Apache is your web server, you can use the techniques in [[PHP Cookbook/Web Basics#Communicating Within Apache|Recipe 8.25]] to communicate with other Apache modules from within your PHP programs.&lt;br /&gt;
&lt;br /&gt;
[[PHP Cookbook/Web Basics#Profiling Code|Recipe 8.26]] demonstrates a few methods for profiling and benchmarking your code. By finding where your programs spend most of their time, you can focus your development efforts on improving the code that has the most noticeable speed-up effect to your users.&lt;br /&gt;
&lt;br /&gt;
This chapter also includes two programs that assist in web site maintenance. Program [[PHP Cookbook/Web Basics#Program: Website Account (De)activator|Recipe 8.27]] validates user accounts by sending an email message with a customized link to each new user. If the user doesn't visit the link within a week of receiving the message, the account is deleted. Program [[PHP Cookbook/Web Basics#Program: Abusive User Checker|Recipe 8.28]] monitors requests in real time on a per-user basis and blocks requests from users that flood your site with traffic.&lt;br /&gt;
&lt;br /&gt;
== Setting Cookies ==&lt;br /&gt;
&lt;br /&gt;
=== Problem ===&lt;br /&gt;
&lt;br /&gt;
You want to set a cookie.&lt;br /&gt;
&lt;br /&gt;
=== Solution ===&lt;br /&gt;
&lt;br /&gt;
Use &amp;lt;tt&amp;gt;setcookie( )&amp;lt;/tt&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
 setcookie('flavor','chocolate chip');&lt;br /&gt;
&lt;br /&gt;
=== Discussion ===&lt;br /&gt;
&lt;br /&gt;
Cookies are sent with the HTTP headers, so &amp;lt;tt&amp;gt;setcookie( )&amp;lt;/tt&amp;gt; must be called before any output is generated.&lt;br /&gt;
&lt;br /&gt;
You can pass additional arguments to &amp;lt;tt&amp;gt;setcookie( )&amp;lt;/tt&amp;gt; to control cookie behavior. The third argument to &amp;lt;tt&amp;gt;setcookie( )&amp;lt;/tt&amp;gt; is an expiration time, expressed as an epoch timestamp. For example, this cookie expires at noon GMT on December 3, 2004:&lt;br /&gt;
&lt;br /&gt;
 setcookie('flavor','chocolate chip',1102075200);&lt;br /&gt;
&lt;br /&gt;
If the third argument to &amp;lt;tt&amp;gt;setcookie( )&amp;lt;/tt&amp;gt; is missing (or empty), the cookie expires when the browser is closed. Also, many systems can't handle a cookie expiration time greater than 2147483647, because that's the largest epoch timestamp that fits in a 32-bit integer, as discussed in the introduction to [[PHP Cookbook/Dates and Times|Chapter 3]].&lt;br /&gt;
&lt;br /&gt;
The fourth argument to &amp;lt;tt&amp;gt;setcookie( )&amp;lt;/tt&amp;gt; is a path. The cookie is sent back to the server only when pages whose path begin with the specified string are requested. For example, the following cookie is sent back only to pages whose path begins with ''/products/'':&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;setcookie('flavor','chocolate chip','','/products/');&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The page that's setting this cookie doesn't have to have a URL that begins with ''/products/'', but the following cookie is sent back only to pages that do.&lt;br /&gt;
&lt;br /&gt;
The fifth argument to &amp;lt;tt&amp;gt;setcookie( )&amp;lt;/tt&amp;gt; is a domain. The cookie is sent back to the server only when pages whose hostname ends with the specified domain are requested. For example, the first cookie in the following code is sent back to all hosts in the ''example.com'' domain, but the second cookie is sent only with requests to the host ''jeannie.example.com'':&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;setcookie('flavor','chocolate chip','','','.example.com');&lt;br /&gt;
setcookie('flavor','chocolate chip','','','jeannie.example.com');&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the first cookie's domain was just ''example.com'' instead of ''.example.com'', it would be sent only to the single host ''example.com'' (and not ''www.example.com'' or ''jeannie.example.com'').&lt;br /&gt;
&lt;br /&gt;
The last optional argument to &amp;lt;tt&amp;gt;setcookie( )&amp;lt;/tt&amp;gt; is a flag that if set to 1, instructs the browser only to send the cookie over an SSL connection. This can be useful if the cookie contains sensitive information, but remember that the data in the cookie is stored in the clear on the user's computer.&lt;br /&gt;
&lt;br /&gt;
Different browsers handle cookies in slightly different ways, especially with regard to how strictly they match path and domain strings and how they determine priority between different cookies of the same name. The &amp;lt;tt&amp;gt;setcookie( )&amp;lt;/tt&amp;gt; page of the online manual has helpful clarifications of these differences.&lt;br /&gt;
&lt;br /&gt;
=== See Also ===&lt;br /&gt;
&lt;br /&gt;
[[PHP Cookbook/Web Basics#Reading Cookie Values|Recipe 8.3]] shows how to read cookie values; [[PHP Cookbook/Web Basics#Deleting Cookies|Recipe 8.4]] shows how to delete cookies; [[PHP Cookbook/Web Basics#Buffering Output to the Browser|Recipe 8.13]] explains output buffering; [[PHP Cookbook/Web Basics#Eliminating &amp;quot;headers already sent&amp;quot; Errors|Recipe 8.19]] shows how to avoid the &amp;quot;headers already sent&amp;quot; error message that sometimes occurs when calling &amp;lt;tt&amp;gt;setcookie( )&amp;lt;/tt&amp;gt;; documentation on &amp;lt;tt&amp;gt;setcookie( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/setcookie''; an expanded cookie specification is detailed in RFC 2965 at ''http://www.faqs.org/rfcs/rfc2965.html''.&lt;br /&gt;
&lt;br /&gt;
== Reading Cookie Values ==&lt;br /&gt;
&lt;br /&gt;
=== Problem ===&lt;br /&gt;
&lt;br /&gt;
You want to read the value of a cookie that's been previously set.&lt;br /&gt;
&lt;br /&gt;
=== Solution ===&lt;br /&gt;
&lt;br /&gt;
Look in the &amp;lt;tt&amp;gt;$_COOKIE&amp;lt;/tt&amp;gt; superglobal array:&lt;br /&gt;
&lt;br /&gt;
 if (isset($_COOKIE['flavor'])) {&lt;br /&gt;
     print &amp;quot;You ate a $_COOKIE[flavor] cookie.&amp;quot;;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
=== Discussion ===&lt;br /&gt;
&lt;br /&gt;
A cookie's value isn't available in &amp;lt;tt&amp;gt;$_COOKIE&amp;lt;/tt&amp;gt; during the request in which the cookie is set. In other words, the &amp;lt;tt&amp;gt;setcookie( )&amp;lt;/tt&amp;gt; function doesn't alter the value of &amp;lt;tt&amp;gt;$_COOKIE&amp;lt;/tt&amp;gt;. On subsequent requests, however, each cookie is stored in &amp;lt;tt&amp;gt;$_COOKIE&amp;lt;/tt&amp;gt;. If &amp;lt;tt&amp;gt;register_globals&amp;lt;/tt&amp;gt; is &amp;lt;tt&amp;gt;on&amp;lt;/tt&amp;gt;, cookie values are also assigned to global variables.&lt;br /&gt;
&lt;br /&gt;
When a browser sends a cookie back to the server, it sends only the value. You can't access the cookie's domain, path, expiration time, or secure status through &amp;lt;tt&amp;gt;$_COOKIE&amp;lt;/tt&amp;gt; because the browser doesn't send that to the server.&lt;br /&gt;
&lt;br /&gt;
To print the names and values of all cookies sent in a particular request, loop through the &amp;lt;tt&amp;gt;$_COOKIE&amp;lt;/tt&amp;gt; array:&lt;br /&gt;
&lt;br /&gt;
 foreach ($_COOKIE as $cookie_name =&amp;gt; $cookie_value) {&lt;br /&gt;
     print &amp;quot;$cookie_name = $cookie_value&amp;amp;lt;br&amp;gt;&amp;quot;;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
=== See Also ===&lt;br /&gt;
&lt;br /&gt;
[[PHP Cookbook/Web Basics#Setting Cookies|Recipe 8.2]] shows how to set cookies; [[PHP Cookbook/Web Basics#Deleting Cookies|Recipe 8.4]] shows how to delete cookies; [[PHP Cookbook/Web Basics#Buffering Output to the Browser|Recipe 8.13]] explains output buffering; [[PHP Cookbook/Web Basics#Eliminating &amp;quot;headers already sent&amp;quot; Errors|Recipe 8.19]] shows how to avoid the &amp;quot;headers already sent&amp;quot; error message that sometimes occurs when calling &amp;lt;tt&amp;gt;setcookie( )&amp;lt;/tt&amp;gt;; [[PHP Cookbook/Forms#Securing PHP's Form Processing|Recipe 9.8]] for information on &amp;lt;tt&amp;gt;register_globals&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Deleting Cookies ==&lt;br /&gt;
&lt;br /&gt;
=== Problem ===&lt;br /&gt;
&lt;br /&gt;
You want to delete a cookie so a browser doesn't send it back to the server.&lt;br /&gt;
&lt;br /&gt;
=== Solution ===&lt;br /&gt;
&lt;br /&gt;
Call &amp;lt;tt&amp;gt;setcookie( )&amp;lt;/tt&amp;gt; with no value for the cookie and an expiration time in the past:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;setcookie('flavor','',time()-86400);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Discussion ===&lt;br /&gt;
&lt;br /&gt;
It's a good idea to make the expiration time a few hours or an entire day in the past, in case your server and the user's computer have unsynchronized clocks. For example, if your server thinks it's 3:06 P.M. and a user's computer thinks it's 3:02 P.M., a cookie with an expiration time of 3:05 P.M. isn't deleted by that user's computer even though the time is in the past for the server.&lt;br /&gt;
&lt;br /&gt;
The call to &amp;lt;tt&amp;gt;setcookie( )&amp;lt;/tt&amp;gt; that deletes a cookie has to have the same arguments (except for value and time) that the call to &amp;lt;tt&amp;gt;setcookie( )&amp;lt;/tt&amp;gt; that set the cookie did, so include the path, domain, and secure flag if necessary.&lt;br /&gt;
&lt;br /&gt;
=== See Also ===&lt;br /&gt;
&lt;br /&gt;
[[PHP Cookbook/Web Basics#Setting Cookies|Recipe 8.2]] shows how to set cookies; [[PHP Cookbook/Web Basics#Reading Cookie Values|Recipe 8.3]] shows how to read cookie values; [[PHP Cookbook/Web Basics#Buffering Output to the Browser|Recipe 8.13]] explains output buffering; [[PHP Cookbook/Web Basics#Eliminating &amp;quot;headers already sent&amp;quot; Errors|Recipe 8.19]] shows how to avoid the &amp;quot;headers already sent&amp;quot; error message that sometimes occurs when calling &amp;lt;tt&amp;gt;setcookie( )&amp;lt;/tt&amp;gt;; documentation on &amp;lt;tt&amp;gt;setcookie( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/setcookie''.&lt;br /&gt;
&lt;br /&gt;
== Redirecting to a Different Location ==&lt;br /&gt;
&lt;br /&gt;
=== Problem ===&lt;br /&gt;
&lt;br /&gt;
You want to automatically send a user to a new URL. For example, after successfully saving form data, you want to redirect a user to a page that confirms the data.&lt;br /&gt;
&lt;br /&gt;
=== Solution ===&lt;br /&gt;
&lt;br /&gt;
Before any output is printed, use &amp;lt;tt&amp;gt;header( )&amp;lt;/tt&amp;gt; to send a &amp;lt;tt&amp;gt;Location&amp;lt;/tt&amp;gt; header with the new URL:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;header('Location: http://www.example.com/');&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Discussion ===&lt;br /&gt;
&lt;br /&gt;
If you want to pass variables to the new page, you can include them in the query string of the URL:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;header('Location: http://www.example.com/?monkey=turtle');&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The URL that you are redirecting a user to is retrieved with GET. You can't redirect someone to retrieve a URL via POST. You can, however, send other headers along with the &amp;lt;tt&amp;gt;Location&amp;lt;/tt&amp;gt; header. This is especially useful with the &amp;lt;tt&amp;gt;Window-target&amp;lt;/tt&amp;gt; header, which indicates a particular named frame or window in which to load the new URL:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;header('Window-target: main');&lt;br /&gt;
header('Location: http://www.example.com/');&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The redirect URL must include the protocol and hostname; it can't just be a pathname:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;// Good Redirect&lt;br /&gt;
header('Location: http://www.example.com/catalog/food/pemmican.php');&lt;br /&gt;
&lt;br /&gt;
// Bad Redirect&lt;br /&gt;
header('Location: /catalog/food/pemmican.php');&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== See Also ===&lt;br /&gt;
&lt;br /&gt;
Documentation on &amp;lt;tt&amp;gt;header( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/header''.&lt;br /&gt;
&lt;br /&gt;
== Using Session Tracking ==&lt;br /&gt;
&lt;br /&gt;
=== Problem ===&lt;br /&gt;
&lt;br /&gt;
You want to maintain information about a user as she moves through your site.&lt;br /&gt;
&lt;br /&gt;
=== Solution ===&lt;br /&gt;
&lt;br /&gt;
Use the session module. The &amp;lt;tt&amp;gt;session_start( )&amp;lt;/tt&amp;gt; function initializes a session, and accessing an element in the global &amp;lt;tt&amp;gt;$_SESSION&amp;lt;/tt&amp;gt; array tells PHP to keep track of the corresponding variable.&lt;br /&gt;
&lt;br /&gt;
 session_start();&lt;br /&gt;
 $_SESSION['visits']++;&lt;br /&gt;
 print 'You have visited here '.$_SESSION['visits'].' times.';&lt;br /&gt;
&lt;br /&gt;
=== Discussion ===&lt;br /&gt;
&lt;br /&gt;
To start a session automatically on each request, set &amp;lt;tt&amp;gt;session.auto_start&amp;lt;/tt&amp;gt; to &amp;lt;tt&amp;gt;1&amp;lt;/tt&amp;gt; in ''php.ini''. With &amp;lt;tt&amp;gt;session.auto_start&amp;lt;/tt&amp;gt;, there's no need to call &amp;lt;tt&amp;gt;session_start( )&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The session functions keep track of users by issuing them cookies with a randomly generated session IDs. If PHP detects that a user doesn't accept the session ID cookie, it automatically adds the session ID to URLs and forms.&amp;lt;ref&amp;gt;Before PHP 4.2.0, this behavior had to be explicitly enabled by building PHP with the &amp;lt;tt&amp;gt;--enable-trans-sid&amp;lt;/tt&amp;gt; configuration setting.&amp;lt;/ref&amp;gt; For example, consider this code that prints a URL:&lt;br /&gt;
&lt;br /&gt;
 print '&amp;lt;a href=&amp;quot;train.php&amp;quot;&amp;gt;Take the A Train&amp;lt;/a&amp;gt;';&lt;br /&gt;
&lt;br /&gt;
If sessions are enabled, but a user doesn't accept cookies, what's sent to the browser is something like:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;a href=&amp;quot;train.php?PHPSESSID=2eb89f3344520d11969a79aea6bd2fdd&amp;quot;&amp;gt;Take the A Train&amp;lt;/a&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this example, the session name is &amp;lt;tt&amp;gt;PHPSESSID&amp;lt;/tt&amp;gt; and the session ID is &amp;lt;tt&amp;gt;2eb89f3344520d11969a79aea6bd2fdd&amp;lt;/tt&amp;gt;. PHP adds those to the URL so they are passed along to the next page. Forms are modified to include a hidden element that passes the session ID. Redirects with the &amp;lt;tt&amp;gt;Location&amp;lt;/tt&amp;gt; header aren't automatically modified, so you have to add a session ID to them yourself using the &amp;lt;tt&amp;gt;SID&amp;lt;/tt&amp;gt; constant:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;$redirect_url = 'http://www.example.com/airplane.php';&lt;br /&gt;
if (defined('SID') &amp;amp;&amp;amp; (! isset($_COOKIE[session_name()]))) {&lt;br /&gt;
    $redirect_url .= '?' . SID;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
header(&amp;quot;Location: $redirect_url&amp;quot;);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;tt&amp;gt;session_name( )&amp;lt;/tt&amp;gt; function returns the name of the cookie that the session ID is stored in, so this code appends the &amp;lt;tt&amp;gt;SID&amp;lt;/tt&amp;gt; constant only to &amp;lt;tt&amp;gt;$redirect_url&amp;lt;/tt&amp;gt; if the constant is defined, and the session cookie isn't set.&lt;br /&gt;
&lt;br /&gt;
By default, PHP stores session data in files in the ''/tmp'' directory on your server. Each session is stored in its own file. To change the directory in which the files are saved, set the &amp;lt;tt&amp;gt;session.save_path&amp;lt;/tt&amp;gt; configuration directive in ''php.ini'' to the new directory. You can also call &amp;lt;tt&amp;gt;session_save_path( )&amp;lt;/tt&amp;gt; with the new directory to change directories, but you need to do this before accessing any session variables.&lt;br /&gt;
&lt;br /&gt;
=== See Also ===&lt;br /&gt;
&lt;br /&gt;
Documentation on &amp;lt;tt&amp;gt;session_start( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/session-start'', &amp;lt;tt&amp;gt;session_save_path( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/session-save-path''; the session module has a number of configuration directives that help you do things like manage how long sessions can last and how they are cached; these are detailed in the &amp;quot;Sessions&amp;quot; section of the online manual at ''http://www.php.net/session''.&lt;br /&gt;
&lt;br /&gt;
== Storing Sessions in a Database ==&lt;br /&gt;
&lt;br /&gt;
=== Problem ===&lt;br /&gt;
&lt;br /&gt;
You want to store session data in a database instead of in files. If multiple web servers all have access to the same database, the session data is then mirrored across all the web servers.&lt;br /&gt;
&lt;br /&gt;
=== Solution ===&lt;br /&gt;
&lt;br /&gt;
Set &amp;lt;tt&amp;gt;session.save_handler&amp;lt;/tt&amp;gt; to &amp;lt;tt&amp;gt;user&amp;lt;/tt&amp;gt; in ''php.ini'' and use the &amp;lt;tt&amp;gt;pc_DB_Session&amp;lt;/tt&amp;gt; class shown in [[PHP Cookbook/Web Basics#phpckbk-CHP-8-EX-1|Example 8-1]]. For example:&lt;br /&gt;
&lt;br /&gt;
 $s = new pc_DB_Session('mysql://user:password@localhost/db');&lt;br /&gt;
 ini_get('session.auto_start') or session_start();&lt;br /&gt;
&lt;br /&gt;
=== Discussion ===&lt;br /&gt;
&lt;br /&gt;
One of the most powerful aspects of the session module is its abstraction of how sessions get saved. The &amp;lt;tt&amp;gt;session_set_save_handler( )&amp;lt;/tt&amp;gt; function tells PHP to use different functions for the various session operations such as saving a session and reading session data. The &amp;lt;tt&amp;gt;pc_DB_Session&amp;lt;/tt&amp;gt; class stores the session data in a database. If this database is shared between multiple web servers, users' session information is portable across all those web servers. So, if you have a bunch of web servers behind a load balancer, you don't need any fancy tricks to ensure that a user's session data is accurate no matter which web server they get sent to.&lt;br /&gt;
&lt;br /&gt;
To use &amp;lt;tt&amp;gt;pc_DB_Session&amp;lt;/tt&amp;gt;, pass a data source name (DSN) to the class when you instantiate it. The session data is stored in a table called &amp;lt;tt&amp;gt;php_session&amp;lt;/tt&amp;gt; whose structure is:&lt;br /&gt;
&lt;br /&gt;
 CREATE TABLE php_session (&lt;br /&gt;
   id CHAR(32) NOT NULL,&lt;br /&gt;
   data MEDIUMBLOB,&lt;br /&gt;
   last_access INT UNSIGNED NOT NULL,&lt;br /&gt;
   PRIMARY KEY(id)&lt;br /&gt;
 )&lt;br /&gt;
&lt;br /&gt;
If you want the table name to be different than &amp;lt;tt&amp;gt;php_session&amp;lt;/tt&amp;gt;, set &amp;lt;tt&amp;gt;session.save_path&amp;lt;/tt&amp;gt; in ''php.ini'' to your new table name. [[PHP Cookbook/Web Basics#phpckbk-CHP-8-EX-1|Example 8-1]] shows the &amp;lt;tt&amp;gt;pc_DB_Session&amp;lt;/tt&amp;gt; class.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;phpckbk-CHP-8-EX-1&amp;quot;&amp;gt;&lt;br /&gt;
'''Example 8-1. pc_DB_Session class'''&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;require 'PEAR.php';&lt;br /&gt;
require 'DB.php';&lt;br /&gt;
&lt;br /&gt;
class pc_DB_Session extends PEAR {&lt;br /&gt;
&lt;br /&gt;
    var $_dbh;&lt;br /&gt;
    var $_table;&lt;br /&gt;
    var $_connected = false;&lt;br /&gt;
    var $_gc_maxlifetime;&lt;br /&gt;
    var $_prh_read;&lt;br /&gt;
    var $error = null;&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Constructor&lt;br /&gt;
     */&lt;br /&gt;
    function pc_DB_Session($dsn = null) {&lt;br /&gt;
        if (is_null($dsn)) { &lt;br /&gt;
            $this-&amp;gt;error = PEAR::raiseError('No DSN specified');&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $this-&amp;gt;_gc_maxlifetime = ini_get('session.gc_maxlifetime');&lt;br /&gt;
        // Sessions last for a day unless otherwise specified. &lt;br /&gt;
        if (! $this-&amp;gt;_gc_maxlifetime) {&lt;br /&gt;
            $this-&amp;gt;_gc_maxlifetime = 86400;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $this-&amp;gt;_table = ini_get('session.save_path');&lt;br /&gt;
        if ((! $this-&amp;gt;_table) || ('/tmp' == $this-&amp;gt;_table)) {&lt;br /&gt;
            $this-&amp;gt;_table = 'php_session';&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $this-&amp;gt;_dbh = DB::connect($dsn);&lt;br /&gt;
        if (DB::isError($this-&amp;gt;_dbh)) {&lt;br /&gt;
            $this-&amp;gt;error = $this-&amp;gt;_dbh;&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $this-&amp;gt;_prh_read = $this-&amp;gt;_dbh-&amp;gt;prepare(&lt;br /&gt;
            &amp;quot;SELECT data FROM $this-&amp;gt;_table WHERE id LIKE ? AND last_access &amp;gt;= ?&amp;quot;);&lt;br /&gt;
        if (DB::isError($this-&amp;gt;_prh_read)) {&lt;br /&gt;
            $this-&amp;gt;error = $this-&amp;gt;_prh_read;&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (! session_set_save_handler(array(&amp;amp;$this,'_open'),&lt;br /&gt;
                                       array(&amp;amp;$this,'_close'),&lt;br /&gt;
                                       array(&amp;amp;$this,'_read'),&lt;br /&gt;
                                       array(&amp;amp;$this,'_write'),&lt;br /&gt;
                                       array(&amp;amp;$this,'_destroy'),&lt;br /&gt;
                                       array(&amp;amp;$this,'_gc'))) {&lt;br /&gt;
            $this-&amp;gt;error = PEAR::raiseError('session_set_save_handler() failed');&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return $this-&amp;gt;_connected = true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function _open() {&lt;br /&gt;
        return $this-&amp;gt;_connected;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    function _close() {&lt;br /&gt;
        return $this-&amp;gt;_connected;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function _read($id) {&lt;br /&gt;
        if (! $this-&amp;gt;_connected) { return false; }&lt;br /&gt;
        $sth = &lt;br /&gt;
            $this-&amp;gt;_dbh-&amp;gt;execute($this-&amp;gt;_prh_read,&lt;br /&gt;
                                 array($id,time() - $this-&amp;gt;_gc_maxlifetime));&lt;br /&gt;
        if (DB::isError($sth)) {&lt;br /&gt;
            $this-&amp;gt;error = $sth;&lt;br /&gt;
            return '';&lt;br /&gt;
        } else {&lt;br /&gt;
            if (($sth-&amp;gt;numRows() == 1) &amp;amp;&amp;amp; &lt;br /&gt;
                ($ar = $sth-&amp;gt;fetchRow(DB_FETCHMODE_ORDERED))) {&lt;br /&gt;
                return $ar[0];&lt;br /&gt;
            } else {&lt;br /&gt;
                return '';&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function _write($id,$data) {&lt;br /&gt;
        $sth = $this-&amp;gt;_dbh-&amp;gt;query(&lt;br /&gt;
            &amp;quot;REPLACE INTO $this-&amp;gt;_table (id,data,last_access) VALUES (?,?,?)&amp;quot;, &lt;br /&gt;
            array($id,$data,time()));&lt;br /&gt;
        if (DB::isError($sth)) {&lt;br /&gt;
            $this-&amp;gt;error = $sth;&lt;br /&gt;
            return false;&lt;br /&gt;
        } else {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function _destroy($id) {&lt;br /&gt;
        $sth = $this-&amp;gt;_dbh-&amp;gt;query(&amp;quot;DELETE FROM $this-&amp;gt;_table WHERE id LIKE ?&amp;quot;,&lt;br /&gt;
                                  array($id));&lt;br /&gt;
        if (DB::isError($sth)) {&lt;br /&gt;
            $this-&amp;gt;error = $sth;&lt;br /&gt;
            return false;&lt;br /&gt;
        } else {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function _gc($maxlifetime) {&lt;br /&gt;
        $sth = $this-&amp;gt;_dbh-&amp;gt;query(&amp;quot;DELETE FROM $this-&amp;gt;_table WHERE last_access &amp;lt; ?&amp;quot;, &lt;br /&gt;
                                  array(time() - $maxlifetime));&lt;br /&gt;
        if (DB::isError($sth)) {&lt;br /&gt;
            $this-&amp;gt;error = $sth;&lt;br /&gt;
            return false;&lt;br /&gt;
        } else {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;tt&amp;gt;pc_DB_Session::_write( )&amp;lt;/tt&amp;gt; method uses a MySQL-specific SQL command, &amp;lt;tt&amp;gt;REPLACE&amp;lt;/tt&amp;gt; &amp;lt;tt&amp;gt;INTO&amp;lt;/tt&amp;gt;, which updates an existing record or inserts a new one, depending on whether there is already a record in the database with the given &amp;lt;tt&amp;gt;id&amp;lt;/tt&amp;gt; field. If you use a different database, modify the &amp;lt;tt&amp;gt;_write( )&amp;lt;/tt&amp;gt; function to accomplish the same task. For instance, delete the existing row (if any), and insert a new one, all inside a transaction:&lt;br /&gt;
&lt;br /&gt;
     function _write($id,$data) {&lt;br /&gt;
         $sth = $this-&amp;gt;_dbh-&amp;gt;query('BEGIN WORK');&lt;br /&gt;
         if (DB::isError($sth)) {&lt;br /&gt;
             $this-&amp;gt;error = $sth;&lt;br /&gt;
             return false;&lt;br /&gt;
         }        &lt;br /&gt;
         $sth = $this-&amp;gt;_dbh-&amp;gt;query(&amp;quot;DELETE FROM $this-&amp;gt;_table WHERE id LIKE ?&amp;quot;,&lt;br /&gt;
                                   array($id));&lt;br /&gt;
         if (DB::isError($sth)) {&lt;br /&gt;
             $this-&amp;gt;error = $sth;&lt;br /&gt;
             $this-&amp;gt;_dbh-&amp;gt;query('ROLLBACK');&lt;br /&gt;
             return false;&lt;br /&gt;
         }        &lt;br /&gt;
         $sth = $this-&amp;gt;_dbh-&amp;gt;query(&lt;br /&gt;
             &amp;quot;INSERT INTO $this-&amp;gt;_table (id,data,last_access) VALUES (?,?,?)&amp;quot;, &lt;br /&gt;
             array($id,$data,time()));&lt;br /&gt;
         if (DB::isError($sth)) {&lt;br /&gt;
             $this-&amp;gt;error = $sth;&lt;br /&gt;
             $this-&amp;gt;_dbh-&amp;gt;query('ROLLBACK');&lt;br /&gt;
             return false;&lt;br /&gt;
         }&lt;br /&gt;
         $sth = $this-&amp;gt;_dbh-&amp;gt;query('COMMIT');&lt;br /&gt;
         if (DB::isError($sth)) {&lt;br /&gt;
             $this-&amp;gt;error = $sth;&lt;br /&gt;
             $this-&amp;gt;_dbh-&amp;gt;query('ROLLBACK');&lt;br /&gt;
             return false;&lt;br /&gt;
         }&lt;br /&gt;
              return true;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
=== See Also ===&lt;br /&gt;
&lt;br /&gt;
Documentation on &amp;lt;tt&amp;gt;session_set_save_handler( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/session-set-save-handler''; a handler using PostgreSQL is available at ''http://www.zend.com/codex.php?id=456&amp;amp;single=1''; the format for data source names is discussed in [[PHP Cookbook/Database Access#Connecting to a SQL Database|Recipe 10.4]].&lt;br /&gt;
&lt;br /&gt;
== Detecting Different Browsers ==&lt;br /&gt;
&lt;br /&gt;
=== Problem ===&lt;br /&gt;
&lt;br /&gt;
You want to generate content based on the capabilities of a user's browser.&lt;br /&gt;
&lt;br /&gt;
=== Solution ===&lt;br /&gt;
&lt;br /&gt;
Use the object returned by &amp;lt;tt&amp;gt;get_browser( )&amp;lt;/tt&amp;gt; to determine a browser's capabilities:&lt;br /&gt;
&lt;br /&gt;
 $browser = get_browser( );&lt;br /&gt;
 &lt;br /&gt;
 if ($browser-&amp;gt;frames) {&lt;br /&gt;
     // print out a frame-based layout&lt;br /&gt;
 } elseif ($browser-&amp;gt;tables) {&lt;br /&gt;
     // print out a table-based layout&lt;br /&gt;
 } else {&lt;br /&gt;
     // print out a boring layout&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
=== Discussion ===&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;tt&amp;gt;get_browser( )&amp;lt;/tt&amp;gt; function examines the environment variable &amp;lt;tt&amp;gt;$_ENV['HTTP_USER_AGENT']&amp;lt;/tt&amp;gt; (set by the web server) and compares it to browsers listed in an external browser capability file. Due to licensing issues, PHP isn't distributed with a browser capability file. The &amp;quot;Obtaining PHP&amp;quot; section of the PHP FAQ (''http://www.php.net/faq.obtaining'') lists ''http://www.cyscape.com/asp/browscap/'' and ''http://www.amrein. com/apps/page.asp?Q=InowDownload'' as sources for a browser capabilities file, and there is also one at ''http://asp.net.do/browscap.zip''.&lt;br /&gt;
&lt;br /&gt;
Once you download a browser capability file, you need to tell PHP where to find it by setting the &amp;lt;tt&amp;gt;browscap&amp;lt;/tt&amp;gt; configuration directive to the pathname of the file. If you use PHP as a CGI, set the directive in the ''php.ini'' file:&lt;br /&gt;
&lt;br /&gt;
 browscap=/usr/local/lib/browscap.txt&lt;br /&gt;
&lt;br /&gt;
If you use Apache, you need to set the directive in your Apache configuration file:&lt;br /&gt;
&lt;br /&gt;
 php_value browscap &amp;quot;/usr/local/lib/browscap.txt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Many of the capabilities &amp;lt;tt&amp;gt;get_browser( )&amp;lt;/tt&amp;gt; finds are shown in [[PHP Cookbook/Web Basics#phpckbk-CHP-8-TABLE-1|Table 8-1]]. For user-configurable capabilities such as &amp;lt;tt&amp;gt;javascript&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;cookies&amp;lt;/tt&amp;gt; though, &amp;lt;tt&amp;gt;get_browser( )&amp;lt;/tt&amp;gt; just tells you if the browser can support those functions. It doesn't tell you if the user has disabled the functions. If JavaScript is turned off in a JavaScript-capable browser or a user refuses to accept cookies when the browser prompts him, &amp;lt;tt&amp;gt;get_browser( )&amp;lt;/tt&amp;gt; still indicates that the browser supports those functions.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;phpckbk-CHP-8-TABLE-1&amp;quot;&amp;gt;&lt;br /&gt;
'''Table 8-1. Browser capability object properties'''&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; &lt;br /&gt;
|-&lt;br /&gt;
! Property !! Description&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;platform&amp;lt;/tt&amp;gt; || Operating system the browser is running on (e.g., Windows, Macintosh, UNIX, Win32, Linux, MacPPC)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;version&amp;lt;/tt&amp;gt; || Full browser version (e.g., 5.0, 3.5, 6.0b2)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;majorver&amp;lt;/tt&amp;gt; || Major browser version (e.g., 5, 3, 6)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;minorver&amp;lt;/tt&amp;gt; || Minor browser version (e.g., 0, 5, 02)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;frames&amp;lt;/tt&amp;gt; || 1 if the browser supports frames&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;tables&amp;lt;/tt&amp;gt; || 1 if the browser supports tables&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;cookies&amp;lt;/tt&amp;gt; || 1 if the browser supports cookies&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;backgroundsounds&amp;lt;/tt&amp;gt; || 1 if the browser supports background sounds with &amp;lt;tt&amp;gt;&amp;lt;embed&amp;gt;&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;&amp;lt;bgsound&amp;gt;&amp;lt;/tt&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;vbscript&amp;lt;/tt&amp;gt; || 1 if the browser supports VBScript&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;javascript&amp;lt;/tt&amp;gt; || 1 if the browser supports JavaScript&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;javaapplets&amp;lt;/tt&amp;gt; || 1 if the browser can run Java applets&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;activexcontrols&amp;lt;/tt&amp;gt; || 1 if the browser can run ActiveX controls&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== See Also ===&lt;br /&gt;
&lt;br /&gt;
Documentation on &amp;lt;tt&amp;gt;get_browser( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/get-browser''.&lt;br /&gt;
&lt;br /&gt;
== Building a GET Query String ==&lt;br /&gt;
&lt;br /&gt;
=== Problem ===&lt;br /&gt;
&lt;br /&gt;
You need to construct a link that includes name/value pairs in a query string.&lt;br /&gt;
&lt;br /&gt;
=== Solution ===&lt;br /&gt;
&lt;br /&gt;
Encode the names and values with &amp;lt;tt&amp;gt;urlencode( )&amp;lt;/tt&amp;gt; and use &amp;lt;tt&amp;gt;join( )&amp;lt;/tt&amp;gt; to create the query string:&lt;br /&gt;
&lt;br /&gt;
 $vars = array('name' =&amp;gt; 'Oscar the Grouch',&lt;br /&gt;
               'color' =&amp;gt; 'green',&lt;br /&gt;
               'favorite_punctuation' =&amp;gt; '#');&lt;br /&gt;
 $safe_vars = array( );&lt;br /&gt;
 foreach ($vars as $name =&amp;gt; $value) {&lt;br /&gt;
     $safe_vars[ ] = urlencode($name).'='.urlencode($value);&lt;br /&gt;
 }&lt;br /&gt;
 &lt;br /&gt;
 $url = '/muppet/select.php?' . join('&amp;amp;',$safe_vars);&lt;br /&gt;
&lt;br /&gt;
=== Discussion ===&lt;br /&gt;
&lt;br /&gt;
The URL built in the solution is:&lt;br /&gt;
&lt;br /&gt;
 /muppet/select.php?name=Oscar+the+Grouch&amp;amp;color=green&amp;amp;favorite_punctuation=%23&lt;br /&gt;
&lt;br /&gt;
The query string has spaces encoded as &amp;lt;tt&amp;gt;+&amp;lt;/tt&amp;gt;. Special characters such as &amp;lt;tt&amp;gt;#&amp;lt;/tt&amp;gt; are hex-encoded as &amp;lt;tt&amp;gt;%23&amp;lt;/tt&amp;gt; because the ASCII value of &amp;lt;tt&amp;gt;#&amp;lt;/tt&amp;gt; is 35, which is 23 in hexadecimal.&lt;br /&gt;
&lt;br /&gt;
Although &amp;lt;tt&amp;gt;urlencode( )&amp;lt;/tt&amp;gt; prevents any special characters in the variable names or values from disrupting the constructed URL, you may have problems if your variable names begin with the names of HTML entities. Consider this partial URL for retrieving information about a stereo system:&lt;br /&gt;
&lt;br /&gt;
 /stereo.php?speakers=12&amp;amp;cdplayer=52&amp;amp;amp=10&lt;br /&gt;
&lt;br /&gt;
The HTML entity for ampersand (&amp;lt;tt&amp;gt;&amp;amp;&amp;lt;/tt&amp;gt;) is &amp;lt;tt&amp;gt;&amp;amp;amp;amp;&amp;lt;/tt&amp;gt; so a browser may interpret that URL as:&lt;br /&gt;
&lt;br /&gt;
 /stereo.php?speakers=12&amp;amp;cdplayer=52&amp;amp;=10&lt;br /&gt;
&lt;br /&gt;
To prevent embedded entities from corrupting your URLs, you have three choices. The first is to choose variable names that can't be confused with entities, such as &amp;lt;tt&amp;gt;_amp&amp;lt;/tt&amp;gt; instead of &amp;lt;tt&amp;gt;amp&amp;lt;/tt&amp;gt;. The second is to convert characters with HTML entity equivalents to those entities before printing out the URL. Use &amp;lt;tt&amp;gt;htmlentities( )&amp;lt;/tt&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
 $url = '/muppet/select.php?' . htmlentities(join('&amp;amp;',$safe_vars));&lt;br /&gt;
&lt;br /&gt;
The resulting URL is:&lt;br /&gt;
&lt;br /&gt;
 /muppet/select.php?name=Oscar+the+Grouch&amp;amp;color=green&amp;amp;favorite_punctuation=%23&lt;br /&gt;
&lt;br /&gt;
Your third choice is to change the argument separator from &amp;lt;tt&amp;gt;&amp;amp;&amp;lt;/tt&amp;gt; to &amp;lt;tt&amp;gt;;&amp;lt;/tt&amp;gt; by setting the configuration directive &amp;lt;tt&amp;gt;arg_separator.input&amp;lt;/tt&amp;gt; to &amp;lt;tt&amp;gt;;&amp;lt;/tt&amp;gt;. You then join name-value pairs with &amp;lt;tt&amp;gt;;&amp;lt;/tt&amp;gt; to produce a query string:&lt;br /&gt;
&lt;br /&gt;
 /muppet/select.php?name=Oscar+the+Grouch;color=green;favorite_punctuation=%23&lt;br /&gt;
&lt;br /&gt;
You may run into trouble with any GET method URLs that you can't explicitly construct with semicolons, such as a form with its method set to GET, because your users' browsers use &amp;lt;tt&amp;gt;&amp;amp;&amp;lt;/tt&amp;gt; as the argument separator.&lt;br /&gt;
&lt;br /&gt;
Because many browsers don't support using &amp;lt;tt&amp;gt;;&amp;lt;/tt&amp;gt; as an argument separator, the easiest way to avoid problems with entities in URLs is to choose variable names that don't overlap with entity names. If you don't have complete control over variable names, however, use &amp;lt;tt&amp;gt;htmlentities( )&amp;lt;/tt&amp;gt; to protect your URLs from entity decoding.&lt;br /&gt;
&lt;br /&gt;
=== See Also ===&lt;br /&gt;
&lt;br /&gt;
Documentation on &amp;lt;tt&amp;gt;urlencode( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/urlencode'' and &amp;lt;tt&amp;gt;htmlentities( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/htmlentities''.&lt;br /&gt;
&lt;br /&gt;
== Using HTTP Basic Authentication ==&lt;br /&gt;
&lt;br /&gt;
=== Problem ===&lt;br /&gt;
&lt;br /&gt;
You want to use PHP to protect parts of your web site with passwords. Instead of storing the passwords in an external file and letting the web server handle the authentication, you want the password verification logic to be in a PHP program.&lt;br /&gt;
&lt;br /&gt;
=== Solution ===&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;tt&amp;gt;$_SERVER['PHP_AUTH_USER']&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;$_SERVER['PHP_AUTH_PW']&amp;lt;/tt&amp;gt; global variables contain the username and password supplied by the user, if any. To deny access to a page, send a &amp;lt;tt&amp;gt;WWW-Authenticate&amp;lt;/tt&amp;gt; header identifying the authentication realm as part of a response with status code 401:&lt;br /&gt;
&lt;br /&gt;
 header('WWW-Authenticate: Basic realm=&amp;quot;My Website&amp;quot;');&lt;br /&gt;
 header('HTTP/1.0 401 Unauthorized');&lt;br /&gt;
 echo &amp;quot;You need to enter a valid username and password.&amp;quot;;&lt;br /&gt;
 exit;&lt;br /&gt;
&lt;br /&gt;
=== Discussion ===&lt;br /&gt;
&lt;br /&gt;
When a browser sees a 401 header, it pops up a dialog box for a username and password. Those authentication credentials (the username and password), if accepted by the server, are associated with the realm in the &amp;lt;tt&amp;gt;WWW-Authenticate&amp;lt;/tt&amp;gt; header. Code that checks authentication credentials needs to be executed before any output is sent to the browser, since it might send headers. For example, you can use a function such as &amp;lt;tt&amp;gt;pc_validate( )&amp;lt;/tt&amp;gt; , shown in [[PHP Cookbook/Web Basics#phpckbk-CHP-8-EX-2|Example 8-2]].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;phpckbk-CHP-8-EX-2&amp;quot;&amp;gt;&lt;br /&gt;
'''Example 8-2. pc_validate( )'''&lt;br /&gt;
&lt;br /&gt;
 function pc_validate($user,$pass) {&lt;br /&gt;
     /* replace with appropriate username and password checking,&lt;br /&gt;
        such as checking a database */&lt;br /&gt;
     $users = array('david' =&amp;gt; 'fadj&amp;amp;32',&lt;br /&gt;
                    'adam'  =&amp;gt; '8HEj838');&lt;br /&gt;
 &lt;br /&gt;
     if (isset($users[$user]) &amp;amp;&amp;amp; ($users[$user] == $pass)) {&lt;br /&gt;
         return true;&lt;br /&gt;
     } else {&lt;br /&gt;
         return false;&lt;br /&gt;
     }&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here's an example of how to use &amp;lt;tt&amp;gt;pc_validate()&amp;lt;/tt&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
 if (! pc_validate($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])) {&lt;br /&gt;
     header('WWW-Authenticate: Basic realm=&amp;quot;My Website&amp;quot;');&lt;br /&gt;
     header('HTTP/1.0 401 Unauthorized');&lt;br /&gt;
     echo &amp;quot;You need to enter a valid username and password.&amp;quot;;&lt;br /&gt;
     exit;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Replace the contents of the &amp;lt;tt&amp;gt;pc_validate( )&amp;lt;/tt&amp;gt; function with appropriate logic to determine if a user entered the correct password. You can also change the realm string from &amp;quot;My Website&amp;quot; and the message that gets printed if a user hits &amp;quot;cancel&amp;quot; in their browser's authentication box from &amp;quot;You need to enter a valid username and password.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
HTTP Basic authentication can't be used if you're running PHP as a CGI. If you can't run PHP as a server module, you can use cookie authentication, discussed in [[PHP Cookbook/Web Basics#Using Cookie Authentication|Recipe 8.11]].&lt;br /&gt;
&lt;br /&gt;
Another issue with HTTP Basic authentication is that it provides no simple way for a user to log out, other then to exit his browser. The PHP online manual has a few suggestions for log out methods that work with varying degrees of success with different server and browser combinations at ''http://www.php.net/features.http-auth''.&lt;br /&gt;
&lt;br /&gt;
There is a straightforward way, however, to force a user to log out after a fixed time interval: include a time calculation in the realm string. Browsers use the same username and password combination every time they're asked for credentials in the same realm. By changing the realm name, the browser is forced to ask the user for new credentials. For example, this forces a log out every night at midnight:&lt;br /&gt;
&lt;br /&gt;
 if (! pc_validate($_SERVER['PHP_AUTH_USER'],$_SERVER['PHP_AUTH_PW'])) {&lt;br /&gt;
     $realm = 'My Website for '.date('Y-m-d');&lt;br /&gt;
     header('WWW-Authenticate: Basic realm=&amp;quot;'.$realm.'&amp;quot;');&lt;br /&gt;
     header('HTTP/1.0 401 Unauthorized');&lt;br /&gt;
     echo &amp;quot;You need to enter a valid username and password.&amp;quot;;&lt;br /&gt;
     exit;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
You can also have a user-specific timeout without changing the realm name by storing the time that a user logs in or accesses a protected page. The &amp;lt;tt&amp;gt;pc_validate()&amp;lt;/tt&amp;gt; function in [[PHP Cookbook/Web Basics#phpckbk-CHP-8-EX-3|Example 8-3]] stores login time in a database and forces a log out if it's been more than 15 minutes since the user last requested a protected page.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;phpckbk-CHP-8-EX-3&amp;quot;&amp;gt;&lt;br /&gt;
'''Example 8-3. pc_validate2( )'''&lt;br /&gt;
&lt;br /&gt;
 function pc_validate2($user,$pass) {&lt;br /&gt;
     $safe_user = strtr(addslashes($user),array('_' =&amp;gt; '\_', '%' =&amp;gt; '\%'));&lt;br /&gt;
     $r = mysql_query(&amp;quot;SELECT password,last_access&lt;br /&gt;
                       FROM users WHERE user LIKE '$safe_user'&amp;quot;);&lt;br /&gt;
     &lt;br /&gt;
     if (mysql_numrows($r) == 1) {&lt;br /&gt;
         $ob = mysql_fetch_object($r);&lt;br /&gt;
         if ($ob-&amp;gt;password == $pass) {&lt;br /&gt;
             $now = time();&lt;br /&gt;
             if (($now - $ob-&amp;gt;last_access) &amp;gt; (15 * 60)) {&lt;br /&gt;
                 return false;&lt;br /&gt;
             } else {&lt;br /&gt;
                 // update the last access time&lt;br /&gt;
                 mysql_query(&amp;quot;UPDATE users SET last_access = NOW() &lt;br /&gt;
                              WHERE user LIKE '$safe_user'&amp;quot;);&lt;br /&gt;
                return true;&lt;br /&gt;
             }&lt;br /&gt;
         }&lt;br /&gt;
     } else {&lt;br /&gt;
         return false;&lt;br /&gt;
     }&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&lt;br /&gt;
 if (! pc_validate($_SERVER['PHP_AUTH_USER'],$_SERVER['PHP_AUTH_PW'])) {&lt;br /&gt;
     header('WWW-Authenticate: Basic realm=&amp;quot;My Website&amp;quot;');&lt;br /&gt;
     header('HTTP/1.0 401 Unauthorized');&lt;br /&gt;
     echo &amp;quot;You need to enter a valid username and password.&amp;quot;;&lt;br /&gt;
     exit;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
=== See Also ===&lt;br /&gt;
&lt;br /&gt;
[[PHP Cookbook/Web Basics#Using Cookie Authentication|Recipe 8.11]]; the HTTP Authentication section of the PHP online manual at ''http://www.php.net/features.http-auth''.&lt;br /&gt;
&lt;br /&gt;
== Using Cookie Authentication ==&lt;br /&gt;
&lt;br /&gt;
=== Problem ===&lt;br /&gt;
&lt;br /&gt;
You want more control over the user login procedure, such as presenting your own login form.&lt;br /&gt;
&lt;br /&gt;
=== Solution ===&lt;br /&gt;
&lt;br /&gt;
Store authentication status in a cookie or as part of a session. When a user logs in successfully, put their username in a cookie. Also include a hash of the username and a secret word so a user can't just make up an authentication cookie with a username in it:&lt;br /&gt;
&lt;br /&gt;
 $secret_word = 'if i ate spinach';&lt;br /&gt;
 if (pc_validate($_REQUEST['username'],$_REQUEST['password'])) {&lt;br /&gt;
     setcookie('login', &lt;br /&gt;
               $_REQUEST['username'].','.md5($_REQUEST['username'].$secret_word));&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
=== Discussion ===&lt;br /&gt;
&lt;br /&gt;
When using cookie authentication, you have to display your own login form:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;form method=&amp;quot;post&amp;quot; action=&amp;quot;login.php&amp;quot;&amp;gt;&lt;br /&gt;
 Username: &amp;lt;input type=&amp;quot;text&amp;quot; name=&amp;quot;username&amp;quot;&amp;gt; &amp;amp;lt;br&amp;gt;&lt;br /&gt;
 Password: &amp;lt;input type=&amp;quot;password&amp;quot; name=&amp;quot;password&amp;quot;&amp;gt; &amp;amp;lt;br&amp;gt;&lt;br /&gt;
 &amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;Log In&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;/form&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can use the same &amp;lt;tt&amp;gt;pc_validate( )&amp;lt;/tt&amp;gt; function from the [[PHP Cookbook/Web Basics#Using HTTP Basic Authentication|Recipe 8.10]] to verify the username and password. The only difference is that you pass it &amp;lt;tt&amp;gt;$_REQUEST['username']&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;$_REQUEST['password']&amp;lt;/tt&amp;gt; as the credentials instead of &amp;lt;tt&amp;gt;$_SERVER['PHP_AUTH_USER']&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;$_SERVER['PHP_AUTH_PW']&amp;lt;/tt&amp;gt;. If the password checks out, send back a cookie that contains a username and a hash of the username, and a secret word. The hash prevents a user from faking a login just by sending a cookie with a username in it.&lt;br /&gt;
&lt;br /&gt;
Once the user has logged in, a page just needs to verify that a valid login cookie was sent in order to do special things for that logged-in user:&lt;br /&gt;
&lt;br /&gt;
 unset($username);&lt;br /&gt;
 if ($_COOKIE['login']) {&lt;br /&gt;
     list($c_username,$cookie_hash) = split(',',$_COOKIE['login']);&lt;br /&gt;
     if (md5($c_username.$secret_word) == $cookie_hash) {&lt;br /&gt;
         $username = $c_username;&lt;br /&gt;
     } else {&lt;br /&gt;
         print &amp;quot;You have sent a bad cookie.&amp;quot;;&lt;br /&gt;
     }&lt;br /&gt;
 }&lt;br /&gt;
 &lt;br /&gt;
 if ($username) {&lt;br /&gt;
     print &amp;quot;Welcome, $username.&amp;quot;;&lt;br /&gt;
 } else {&lt;br /&gt;
     print &amp;quot;Welcome, anonymous user.&amp;quot;;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
If you use the built-in session support, you can add the username and hash to the session and avoid sending a separate cookie. When someone logs in, set an additional variable in the session instead of sending a cookie:&lt;br /&gt;
&lt;br /&gt;
 if (pc_validate($_REQUEST['username'],$_REQUEST['password'])) {&lt;br /&gt;
     $_SESSION['login'] = &lt;br /&gt;
         $_REQUEST['username'].','.md5($_REQUEST['username'].$secret_word));&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
The verification code is almost the same; it just uses &amp;lt;tt&amp;gt;$_SESSION&amp;lt;/tt&amp;gt; instead of &amp;lt;tt&amp;gt;$_COOKIE&amp;lt;/tt&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
 unset($username);&lt;br /&gt;
 if ($_SESSION['login']) {&lt;br /&gt;
     list($c_username,$cookie_hash) = explode(',',$_SESSION['login']);&lt;br /&gt;
     if (md5($c_username.$secret_word) == $cookie_hash) {&lt;br /&gt;
         $username = $c_username;&lt;br /&gt;
     } else {&lt;br /&gt;
         print &amp;quot;You have tampered with your session.&amp;quot;;&lt;br /&gt;
     }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Using cookie or session authentication instead of HTTP Basic authentication makes it much easier for users to log out: you just delete their login cookie or remove the login variable from their session. Another advantage of storing authentication information in a session is that you can link users' browsing activities while logged in to their browsing activities before they log in or after they log out. With HTTP Basic authentication, you have no way of tying the requests with a username to the requests that the same user made before they supplied a username. Looking for requests from the same IP address is error-prone, especially if the user is behind a firewall or proxy server. If you are using sessions, you can modify the login procedure to log the connection between session ID and username:&lt;br /&gt;
&lt;br /&gt;
 if (pc_validate($_REQUEST['username'],$_REQUEST['password'])) {&lt;br /&gt;
     $_SESSION['login'] = &lt;br /&gt;
         $_REQUEST['username'].','.md5($_REQUEST['username'].$secret_word));&lt;br /&gt;
     error_log('Session id '.session_id().' log in as '.$_REQUEST['username']);&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
This example writes a message to the error log, but it could just as easily record the information in a database that you could use in your analysis of site usage and traffic.&lt;br /&gt;
&lt;br /&gt;
One danger of using session IDs is that sessions are hijackable. If Alice guesses Bob's session ID, she can masquerade as Bob to the web server. The session module has two optional configuration directives that help you make session IDs harder to guess. The &amp;lt;tt&amp;gt;session.entropy_file&amp;lt;/tt&amp;gt; directive contains a path to a device or file that generates randomness, such as ''/dev/random'' or ''/dev/urandom''. The &amp;lt;tt&amp;gt;session.entropy_length&amp;lt;/tt&amp;gt; directive holds the number of bytes to be read from the entropy file when creating session IDs.&lt;br /&gt;
&lt;br /&gt;
No matter how hard session IDs are to guess, they can also be stolen if they are sent in clear text between your server and a user's browser. HTTP Basic authentication also has this problem. Use SSL to guard against network sniffing, as described in [[PHP Cookbook/Encryption and Security#Detecting SSL|Recipe 14.11]].&lt;br /&gt;
&lt;br /&gt;
=== See Also ===&lt;br /&gt;
&lt;br /&gt;
[[PHP Cookbook/Web Basics#Using HTTP Basic Authentication|Recipe 8.10]]; [[PHP Cookbook/Web Basics#Logging Errors|Recipe 8.18]] discusses logging errors; [[PHP Cookbook/Encryption and Security#Verifying Data with Hashes|Recipe 14.4]] discusses verifying data with hashes; documentation on &amp;lt;tt&amp;gt;setcookie( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/setcookie'' and on &amp;lt;tt&amp;gt;md5( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/md5''.&lt;br /&gt;
&lt;br /&gt;
== Flushing Output to the Browser ==&lt;br /&gt;
&lt;br /&gt;
=== Problem ===&lt;br /&gt;
&lt;br /&gt;
You want to force output to be sent to the browser. For example, before doing a slow database query, you want to give the user a status update.&lt;br /&gt;
&lt;br /&gt;
=== Solution ===&lt;br /&gt;
&lt;br /&gt;
Use &amp;lt;tt&amp;gt;flush( )&amp;lt;/tt&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
 print 'Finding identical snowflakes...';&lt;br /&gt;
 flush();&lt;br /&gt;
 $sth = $dbh-&amp;gt;query(&lt;br /&gt;
     'SELECT shape,COUNT(*) AS c FROM snowflakes GROUP BY shape HAVING c &amp;gt; 1');&lt;br /&gt;
&lt;br /&gt;
=== Discussion ===&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;tt&amp;gt;flush( )&amp;lt;/tt&amp;gt; function sends all output that PHP has internally buffered to the web server, but the web server may have internal buffering of its own that delays when the data reaches the browser. Additionally, some browsers don't display data immediately upon receiving it, and some versions of Internet Explorer don't display a page until they've received at least 256 bytes. To force IE to display content, print blank spaces at the beginning of the page:&lt;br /&gt;
&lt;br /&gt;
 print str_repeat(' ',300);&lt;br /&gt;
 print 'Finding identical snowflakes...';&lt;br /&gt;
 flush();&lt;br /&gt;
 $sth = $dbh-&amp;gt;query(&lt;br /&gt;
     'SELECT shape,COUNT(*) AS c FROM snowflakes GROUP BY shape HAVING c &amp;gt; 1');&lt;br /&gt;
&lt;br /&gt;
=== See Also ===&lt;br /&gt;
&lt;br /&gt;
[[PHP Cookbook/Files#Flushing Output to a File|Recipe 18.18]]; documentation on &amp;lt;tt&amp;gt;flush( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/flush''.&lt;br /&gt;
&lt;br /&gt;
== Buffering Output to the Browser ==&lt;br /&gt;
&lt;br /&gt;
=== Problem ===&lt;br /&gt;
&lt;br /&gt;
You want to start generating output before you're finished sending headers or cookies.&lt;br /&gt;
&lt;br /&gt;
=== Solution ===&lt;br /&gt;
&lt;br /&gt;
Call &amp;lt;tt&amp;gt;ob_start( )&amp;lt;/tt&amp;gt; at the top of your page and &amp;lt;tt&amp;gt;ob_end_flush( )&amp;lt;/tt&amp;gt; at the bottom. You can then intermix commands that generate output and commands that send headers. The output won't be sent until &amp;lt;tt&amp;gt;ob_end_flush( )&amp;lt;/tt&amp;gt; is called:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;?php ob_start(); ?&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
 I haven't decided if I want to send a cookie yet.&lt;br /&gt;
 &lt;br /&gt;
 &amp;lt;?php setcookie('heron','great blue'); ?&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
 Yes, sending that cookie was the right decision.&lt;br /&gt;
 &lt;br /&gt;
 &amp;lt;?php ob_end_flush(); ?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Discussion ===&lt;br /&gt;
&lt;br /&gt;
You can pass &amp;lt;tt&amp;gt;ob_start( )&amp;lt;/tt&amp;gt; the name of a callback function to process the output buffer with that function. This is useful for postprocessing all the content in a page, such as hiding email addresses from address-harvesting robots:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;?php &lt;br /&gt;
 function mangle_email($s) {&lt;br /&gt;
     return preg_replace('/([^@\s]+)@([-a-z0-9]+\.)+[a-z]{2,}/is',&lt;br /&gt;
                         '&amp;lt;$1@...&amp;gt;',&lt;br /&gt;
                         $s);&lt;br /&gt;
 }&lt;br /&gt;
 &lt;br /&gt;
 ob_start('mangle_email'); &lt;br /&gt;
 ?&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
 I would not like spam sent to ronald@example.com!&lt;br /&gt;
 &lt;br /&gt;
 &amp;lt;?php ob_end_flush(); ?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;tt&amp;gt;mangle_email( )&amp;lt;/tt&amp;gt; function transforms the output to:&lt;br /&gt;
&lt;br /&gt;
 I would not like spam sent to &amp;lt;ronald@...&amp;gt;!&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;tt&amp;gt;output_buffering&amp;lt;/tt&amp;gt; configuration directive turns output buffering on for all pages:&lt;br /&gt;
&lt;br /&gt;
 output_buffering = On&lt;br /&gt;
&lt;br /&gt;
Similarly, &amp;lt;tt&amp;gt;output_handler&amp;lt;/tt&amp;gt; sets an output buffer processing callback to be used on all pages:&lt;br /&gt;
&lt;br /&gt;
 output_handler=mangle_email&lt;br /&gt;
&lt;br /&gt;
Setting an &amp;lt;tt&amp;gt;output_handler&amp;lt;/tt&amp;gt; automatically sets &amp;lt;tt&amp;gt;output_buffering&amp;lt;/tt&amp;gt; to &amp;lt;tt&amp;gt;on&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== See Also ===&lt;br /&gt;
&lt;br /&gt;
[[PHP Cookbook/Database Access#Logging Debugging Information and Errors|Recipe 10.11]] uses output buffering in a database error logging function; documentation on &amp;lt;tt&amp;gt;ob_start( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/ob-start'', &amp;lt;tt&amp;gt;ob_end_flush( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/ob-end-flush'', and output buffering at ''http://www.php.net/outcontrol''.&lt;br /&gt;
&lt;br /&gt;
== Compressing Web Output with gzip ==&lt;br /&gt;
&lt;br /&gt;
=== Problem ===&lt;br /&gt;
&lt;br /&gt;
You want to send compressed content to browsers that support automatic decompression.&lt;br /&gt;
&lt;br /&gt;
=== Solution ===&lt;br /&gt;
&lt;br /&gt;
Add this setting to your ''php.ini'' file:&lt;br /&gt;
&lt;br /&gt;
 zlib.output_compression=1&lt;br /&gt;
&lt;br /&gt;
=== Discussion ===&lt;br /&gt;
&lt;br /&gt;
Browsers tell the server that they can accept compressed responses with the &amp;lt;tt&amp;gt;Accept-Encoding&amp;lt;/tt&amp;gt; header. If a browser sends &amp;lt;tt&amp;gt;Accept-Encoding: gzip&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;Accept-Encoding:&amp;lt;/tt&amp;gt; &amp;lt;tt&amp;gt;deflate&amp;lt;/tt&amp;gt;, and PHP is built with the ''zlib'' extension, the &amp;lt;tt&amp;gt;zlib.output_compression&amp;lt;/tt&amp;gt; configuration directive tells PHP to compress the output with the appropriate algorithm before sending it back to the browser. The browser uncompresses the data before displaying it.&lt;br /&gt;
&lt;br /&gt;
You can adjust the compression level with the &amp;lt;tt&amp;gt;zlib.output_compression_level&amp;lt;/tt&amp;gt; configuration directive:&lt;br /&gt;
&lt;br /&gt;
 ; minimal compression&lt;br /&gt;
 zlib.output_compression_level=1&lt;br /&gt;
 &lt;br /&gt;
 ; maximal compression&lt;br /&gt;
 zlib.output_compression_level=9&lt;br /&gt;
&lt;br /&gt;
At higher compression levels, less data needs to be sent from the server to the browser, but more server CPU time must be used to compress the data.&lt;br /&gt;
&lt;br /&gt;
=== See Also ===&lt;br /&gt;
&lt;br /&gt;
Documentation on the ''zlib'' extension at ''http://www.php.net/zlib''.&lt;br /&gt;
&lt;br /&gt;
== Hiding Error Messages from Users ==&lt;br /&gt;
&lt;br /&gt;
=== Problem ===&lt;br /&gt;
&lt;br /&gt;
You don't want PHP error messages visible to users.&lt;br /&gt;
&lt;br /&gt;
=== Solution ===&lt;br /&gt;
&lt;br /&gt;
Set the following values in your ''php.ini'' or web server configuration file:&lt;br /&gt;
&lt;br /&gt;
 display_errors =off&lt;br /&gt;
 log_errors     =on&lt;br /&gt;
&lt;br /&gt;
These settings tell PHP not to display errors as HTML to the browser but to put them in the server's error log.&lt;br /&gt;
&lt;br /&gt;
=== Discussion ===&lt;br /&gt;
&lt;br /&gt;
When &amp;lt;tt&amp;gt;log_errors&amp;lt;/tt&amp;gt; is set to &amp;lt;tt&amp;gt;on&amp;lt;/tt&amp;gt;, error messages are written to the server's error log. If you want PHP errors to be written to a separate file, set the &amp;lt;tt&amp;gt;error_log&amp;lt;/tt&amp;gt; configuration directive with the name of that file:&lt;br /&gt;
&lt;br /&gt;
 error_log   = /var/log/php.error.log&lt;br /&gt;
&lt;br /&gt;
If &amp;lt;tt&amp;gt;error_log&amp;lt;/tt&amp;gt; is set to &amp;lt;tt&amp;gt;syslog&amp;lt;/tt&amp;gt;, PHP error messages are sent to the system logger using ''syslog(3)'' on Unix and to the Event Log on Windows NT.&lt;br /&gt;
&lt;br /&gt;
There are lots of error messages you want to show your users, such as telling them they've filled in a form incorrectly, but you should shield your users from internal errors that may reflect a problem with your code. There are two reasons for this. First, these errors appear unprofessional (to expert users) and confusing (to novice users). If something goes wrong when saving form input to a database, check the return code from the database query and display a message to your users apologizing and asking them to come back later. Showing them a cryptic error message straight from PHP doesn't inspire confidence in your web site.&lt;br /&gt;
&lt;br /&gt;
Second, displaying these errors to users is a security risk. Depending on your database and the type of error, the error message may contain information about how to log in to your database or server and how it is structured. Malicious users can use this information to mount an attack on your web site.&lt;br /&gt;
&lt;br /&gt;
For example, if your database server is down, and you attempt to connect to it with &amp;lt;tt&amp;gt;mysql_connect( )&amp;lt;/tt&amp;gt;, PHP generates the following warning:&lt;br /&gt;
&lt;br /&gt;
 &amp;amp;lt;br&amp;gt;&lt;br /&gt;
 &amp;amp;lt;b&amp;gt;Warning&amp;amp;lt;/b&amp;gt;:  Can't connect to MySQL server on 'db.example.com' (111) in &lt;br /&gt;
 &amp;amp;lt;b&amp;gt;/www/docroot/example.php&amp;amp;lt;/b&amp;gt; on line &amp;amp;lt;b&amp;gt;3&amp;amp;lt;/b&amp;gt;&amp;amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If this warning message is sent to a user's browser, he learns that your database server is called ''db.example.com'' and can mount an attack on it.&lt;br /&gt;
&lt;br /&gt;
=== See Also ===&lt;br /&gt;
&lt;br /&gt;
[[PHP Cookbook/Web Basics#Logging Errors|Recipe 8.18]] for how to log errors; documentation on PHP configuration directives at ''http://www.php.net/configuration''.&lt;br /&gt;
&lt;br /&gt;
== Tuning Error Handling ==&lt;br /&gt;
&lt;br /&gt;
=== Problem ===&lt;br /&gt;
&lt;br /&gt;
You want to alter the error-logging sensitivity on a particular page. This lets you control what types of errors are reported.&lt;br /&gt;
&lt;br /&gt;
=== Solution ===&lt;br /&gt;
&lt;br /&gt;
To adjust the types of errors PHP complains about, use &amp;lt;tt&amp;gt;error_reporting( )&amp;lt;/tt&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
 error_reporting(E_ALL);                // everything&lt;br /&gt;
 error_reporting(E_ERROR | E_PARSE);    // only major problems&lt;br /&gt;
 error_reporting(E_ALL &amp;amp; ~E_NOTICE);    // everything but notices&lt;br /&gt;
&lt;br /&gt;
=== Discussion ===&lt;br /&gt;
&lt;br /&gt;
Every error generated has an error type associated with it. For example, if you try to &amp;lt;tt&amp;gt;array_pop( )&amp;lt;/tt&amp;gt; a string, PHP complains that &amp;quot;This argument needs to be an array,&amp;quot; since you can only pop arrays. The error type associated with this message is &amp;lt;tt&amp;gt;E_NOTICE&amp;lt;/tt&amp;gt;, a nonfatal runtime problem.&lt;br /&gt;
&lt;br /&gt;
By default, the error reporting level is &amp;lt;tt&amp;gt;E_ALL &amp;amp; ~E_NOTICE&amp;lt;/tt&amp;gt;, which means all error types except notices. The &amp;lt;tt&amp;gt;&amp;amp;&amp;lt;/tt&amp;gt; is a logical &amp;lt;tt&amp;gt;AND&amp;lt;/tt&amp;gt;, and the &amp;lt;tt&amp;gt;~&amp;lt;/tt&amp;gt; is a logical &amp;lt;tt&amp;gt;NOT&amp;lt;/tt&amp;gt;. However, the ''php.ini-recommended'' configuration file sets the error reporting level to &amp;lt;tt&amp;gt;E_ALL&amp;lt;/tt&amp;gt;, which is all error types.&lt;br /&gt;
&lt;br /&gt;
Error messages flagged as notices are runtime problems that are less serious than warnings. They're not necessarily wrong, but they indicate a potential problem. One example of an &amp;lt;tt&amp;gt;E_NOTICE&amp;lt;/tt&amp;gt; is &amp;quot;Undefined variable,&amp;quot; which occurs if you try to use a variable without previously assigning it a value:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;// Generates an E_NOTICE&lt;br /&gt;
foreach ($array as $value) {&lt;br /&gt;
    $html .= $value;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Doesn't generate any error message&lt;br /&gt;
$html = '';&lt;br /&gt;
foreach ($array as $value) {&lt;br /&gt;
    $html .= $value;&lt;br /&gt;
}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the first case, the first time though the &amp;lt;tt&amp;gt;foreach&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;$html&amp;lt;/tt&amp;gt; is undefined. So, when you append to it, PHP lets you know you're appending to an undefined variable. In the second case, the empty string is assigned to &amp;lt;tt&amp;gt;$html&amp;lt;/tt&amp;gt; above the loop to avoid the &amp;lt;tt&amp;gt;E_NOTICE&amp;lt;/tt&amp;gt;. The previous two code snippets generate identical code because the default value of a variable is the empty string. The &amp;lt;tt&amp;gt;E_NOTICE&amp;lt;/tt&amp;gt; can be helpful because, for example, you may have misspelled a variable name:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;foreach ($array as $value) {&lt;br /&gt;
    $hmtl .= $value; // oops! that should be $html&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$html = ''&lt;br /&gt;
foreach ($array as $value) {&lt;br /&gt;
    $hmtl .= $value; // oops! that should be $html&lt;br /&gt;
}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A custom error-handling function can parse errors based on their type and take an appropriate action. A complete list of error types is shown in [[PHP Cookbook/Web Basics#phpckbk-CHP-8-TABLE-2|Table 8-2]].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;phpckbk-CHP-8-TABLE-2&amp;quot;&amp;gt;&lt;br /&gt;
'''Table 8-2. Error types'''&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; &lt;br /&gt;
|-&lt;br /&gt;
! Value !! Constant !! Description !! Catchable&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;1&amp;lt;/tt&amp;gt; || &amp;lt;tt&amp;gt;E_ERROR&amp;lt;/tt&amp;gt; || Nonrecoverable error || No&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;2&amp;lt;/tt&amp;gt; || &amp;lt;tt&amp;gt;E_WARNING&amp;lt;/tt&amp;gt; || Recoverable error || Yes&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;4&amp;lt;/tt&amp;gt; || &amp;lt;tt&amp;gt;E_PARSE&amp;lt;/tt&amp;gt; || Parser error || No&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;8&amp;lt;/tt&amp;gt; || &amp;lt;tt&amp;gt;E_NOTICE&amp;lt;/tt&amp;gt; || Possible error || Yes&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;16&amp;lt;/tt&amp;gt; || &amp;lt;tt&amp;gt;E_CORE_ERROR&amp;lt;/tt&amp;gt; || Like &amp;lt;tt&amp;gt;E_ERROR&amp;lt;/tt&amp;gt; but generated by the PHP core || No&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;32&amp;lt;/tt&amp;gt; || &amp;lt;tt&amp;gt;E_CORE_WARNING&amp;lt;/tt&amp;gt; || Like &amp;lt;tt&amp;gt;E_WARNING&amp;lt;/tt&amp;gt; but generated by the PHP core || No&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;64&amp;lt;/tt&amp;gt; || &amp;lt;tt&amp;gt;E_COMPILE_ERROR&amp;lt;/tt&amp;gt; || Like &amp;lt;tt&amp;gt;E_ERROR&amp;lt;/tt&amp;gt; but generated by the Zend Engine || No&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;128&amp;lt;/tt&amp;gt; || &amp;lt;tt&amp;gt;E_COMPILE_WARNING&amp;lt;/tt&amp;gt; || Like &amp;lt;tt&amp;gt;E_WARNING&amp;lt;/tt&amp;gt; but generated by the Zend Engine || No&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;256&amp;lt;/tt&amp;gt; || &amp;lt;tt&amp;gt;E_USER_ERROR&amp;lt;/tt&amp;gt; || Like &amp;lt;tt&amp;gt;E_ERROR&amp;lt;/tt&amp;gt; but triggered by calling &amp;lt;tt&amp;gt;trigger_error( )&amp;lt;/tt&amp;gt; || Yes&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;512&amp;lt;/tt&amp;gt; || &amp;lt;tt&amp;gt;E_USER_WARNING&amp;lt;/tt&amp;gt; || Like &amp;lt;tt&amp;gt;E_WARNING&amp;lt;/tt&amp;gt; but triggered by calling &amp;lt;tt&amp;gt;trigger_error( )&amp;lt;/tt&amp;gt; || Yes&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;1024&amp;lt;/tt&amp;gt; || &amp;lt;tt&amp;gt;E_USER_NOTICE&amp;lt;/tt&amp;gt; || Like &amp;lt;tt&amp;gt;E_NOTICE&amp;lt;/tt&amp;gt; but triggered by calling &amp;lt;tt&amp;gt;trigger_error( )&amp;lt;/tt&amp;gt; || Yes&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;2047&amp;lt;/tt&amp;gt; || &amp;lt;tt&amp;gt;E_ALL&amp;lt;/tt&amp;gt; || Everything || n/a&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Errors labeled catchable can be processed by the function registered using &amp;lt;tt&amp;gt;set_error_handler( )&amp;lt;/tt&amp;gt; . The others indicate such a serious problem that they're not safe to be handled by users, and PHP must take care of them.&lt;br /&gt;
&lt;br /&gt;
=== See Also ===&lt;br /&gt;
&lt;br /&gt;
[[PHP Cookbook/Web Basics#Using a Custom Error Handler|Recipe 8.17]] shows how to set up a custom error handler; documentation on &amp;lt;tt&amp;gt;error_reporting( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/error-reporting'' and &amp;lt;tt&amp;gt;set_error_handler( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/set-error-handler''; for more information about errors, see ''http://www.php.net/ref.errorfunc.php''.&lt;br /&gt;
&lt;br /&gt;
== Using a Custom Error Handler ==&lt;br /&gt;
&lt;br /&gt;
=== Problem ===&lt;br /&gt;
&lt;br /&gt;
You want to create a custom error handler that lets you control how PHP reports errors.&lt;br /&gt;
&lt;br /&gt;
=== Solution ===&lt;br /&gt;
&lt;br /&gt;
To set up your own error function, use &amp;lt;tt&amp;gt;set_error_handler( )&amp;lt;/tt&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
 set_error_handler('pc_error_handler');&lt;br /&gt;
 &lt;br /&gt;
 function pc_error_handler($errno, $error, $file, $line) {&lt;br /&gt;
     $message = &amp;quot;[ERROR][$errno][$error][$file:$line]&amp;quot;;&lt;br /&gt;
     error_log($message);&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
=== Discussion ===&lt;br /&gt;
&lt;br /&gt;
A custom error handling function can parse errors based on their type and take the appropriate action. See [[PHP Cookbook/Web Basics#phpckbk-CHP-8-TABLE-2|Table 8-2]] in [[PHP Cookbook/Web Basics#Tuning Error Handling|Recipe 8.16]] for a list of error types.&lt;br /&gt;
&lt;br /&gt;
Pass &amp;lt;tt&amp;gt;set_error_handler( )&amp;lt;/tt&amp;gt; the name of a function, and PHP forwards all errors to that function. The error handling function can take up to five parameters. The first parameter is the error type, such as &amp;lt;tt&amp;gt;8&amp;lt;/tt&amp;gt; for &amp;lt;tt&amp;gt;E_NOTICE&amp;lt;/tt&amp;gt;. The second is the message thrown by the error, such as &amp;quot;Undefined variable: html&amp;quot;. The third and fourth arguments are the name of the file and the line number in which PHP detected the error. The final parameter is an array holding all the variables defined in the current scope and their values.&lt;br /&gt;
&lt;br /&gt;
For example, in this code &amp;lt;tt&amp;gt;$html&amp;lt;/tt&amp;gt; is appended to without first being assigned an initial value:&lt;br /&gt;
&lt;br /&gt;
 error_reporting(E_ALL);&lt;br /&gt;
 set_error_handler('pc_error_handler');&lt;br /&gt;
 &lt;br /&gt;
 function pc_error_handler($errno, $error, $file, $line, $context) {&lt;br /&gt;
     $message = &amp;quot;[ERROR][$errno][$error][$file:$line]&amp;quot;;&lt;br /&gt;
     print &amp;quot;$message&amp;quot;;&lt;br /&gt;
     print_r($context);&lt;br /&gt;
 }&lt;br /&gt;
 &lt;br /&gt;
 $form = array('one','two');&lt;br /&gt;
 &lt;br /&gt;
 foreach ($form as $line) {&lt;br /&gt;
     $html .= &amp;quot;&amp;amp;lt;b&amp;gt;$line&amp;amp;lt;/b&amp;gt;&amp;quot;;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
When the &amp;quot;Undefined variable&amp;quot; error is generated, &amp;lt;tt&amp;gt;pc_error_handler( )&amp;lt;/tt&amp;gt; prints:&lt;br /&gt;
&lt;br /&gt;
 [ERROR][8][Undefined variable:  html][err-all.php:16]&lt;br /&gt;
&lt;br /&gt;
After the initial error message, &amp;lt;tt&amp;gt;pc_error_handler( )&amp;lt;/tt&amp;gt; also prints a large array containing all the globals, environment, request, and session variables.&lt;br /&gt;
&lt;br /&gt;
Errors labeled catchable in [[PHP Cookbook/Web Basics#phpckbk-CHP-8-TABLE-2|Table 8-2]] can be processed by the function registered using &amp;lt;tt&amp;gt;set_error_handler( )&amp;lt;/tt&amp;gt;. The others indicate such a serious problem that they're not safe to be handled by users and PHP must take care of them.&lt;br /&gt;
&lt;br /&gt;
=== See Also ===&lt;br /&gt;
&lt;br /&gt;
[[PHP Cookbook/Web Basics#Tuning Error Handling|Recipe 8.16]] lists the different error types; documentation on &amp;lt;tt&amp;gt;set_error_handler( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/set-error-handler''.&lt;br /&gt;
&lt;br /&gt;
== Logging Errors ==&lt;br /&gt;
&lt;br /&gt;
=== Problem ===&lt;br /&gt;
&lt;br /&gt;
You want to write program errors to a log. These errors can include everything from parser errors and files not being found to bad database queries and dropped connections.&lt;br /&gt;
&lt;br /&gt;
=== Solution ===&lt;br /&gt;
&lt;br /&gt;
Use &amp;lt;tt&amp;gt;error_log( )&amp;lt;/tt&amp;gt; to write to the error log:&lt;br /&gt;
&lt;br /&gt;
 // LDAP error&lt;br /&gt;
 if (ldap_errno($ldap)) {&lt;br /&gt;
     error_log(&amp;quot;LDAP Error #&amp;quot; . ldap_errno($ldap) . &amp;quot;: &amp;quot; . ldap_error($ldap));&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
=== Discussion ===&lt;br /&gt;
&lt;br /&gt;
Logging errors facilitates debugging. Smart error logging makes it easier to fix bugs. Always log information about what caused the error:&lt;br /&gt;
&lt;br /&gt;
 $r = mysql_query($sql);&lt;br /&gt;
 if (! $r) {&lt;br /&gt;
     $error = mysql_error( );&lt;br /&gt;
     error_log('[DB: query @'.$_SERVER['REQUEST_URI'].&amp;quot;][$sql]: $error&amp;quot;);&lt;br /&gt;
 } else {&lt;br /&gt;
     // process results&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
You're not getting all the debugging help you could be if you simply log that an error occurred without any supporting information:&lt;br /&gt;
&lt;br /&gt;
 $r = mysql_query($sql);&lt;br /&gt;
 if (! $r) {&lt;br /&gt;
     error_log(&amp;quot;bad query&amp;quot;);&lt;br /&gt;
 } else {&lt;br /&gt;
     // process result&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Another useful technique is to include the &amp;lt;tt&amp;gt;_ _FILE_ _&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;_ _LINE_ _&amp;lt;/tt&amp;gt; constants in your error messages:&lt;br /&gt;
&lt;br /&gt;
 error_log('['._ _FILE_ _.']['._ _LINE_ _.&amp;quot;]: $error&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;tt&amp;gt;_ _FILE_ _&amp;lt;/tt&amp;gt; constant is the current filename, and &amp;lt;tt&amp;gt;_ _LINE_ _&amp;lt;/tt&amp;gt; is the current line number.&lt;br /&gt;
&lt;br /&gt;
=== See Also ===&lt;br /&gt;
&lt;br /&gt;
[[PHP Cookbook/Web Basics#Hiding Error Messages from Users|Recipe 8.15]] for hiding error messages from users; documentation on &amp;lt;tt&amp;gt;error_log( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/error-log''.&lt;br /&gt;
&lt;br /&gt;
== Eliminating &amp;quot;headers already sent&amp;quot; Errors ==&lt;br /&gt;
&lt;br /&gt;
=== Problem ===&lt;br /&gt;
&lt;br /&gt;
You are trying to send a HTTP header or cookie using &amp;lt;tt&amp;gt;header( )&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;setcookie( )&amp;lt;/tt&amp;gt;, but PHP reports a &amp;quot;headers already sent&amp;quot; error message.&lt;br /&gt;
&lt;br /&gt;
=== Solution ===&lt;br /&gt;
&lt;br /&gt;
This error happens when you send nonheader output before calling &amp;lt;tt&amp;gt;header( )&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;setcookie( )&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Rewrite your code so any output happens after sending headers:&lt;br /&gt;
&lt;br /&gt;
 // good&lt;br /&gt;
 setcookie(&amp;quot;name&amp;quot;, $name);&lt;br /&gt;
 print &amp;quot;Hello $name!&amp;quot;;&lt;br /&gt;
 &lt;br /&gt;
 // bad&lt;br /&gt;
 print &amp;quot;Hello $name!&amp;quot;;&lt;br /&gt;
 setcookie(&amp;quot;name&amp;quot;, $name);&lt;br /&gt;
 &lt;br /&gt;
 // good&lt;br /&gt;
 &amp;lt;?php setcookie(&amp;quot;name&amp;quot;,$name); ?&amp;gt;&lt;br /&gt;
 &amp;lt;html&amp;gt;&amp;lt;title&amp;gt;Hello&amp;lt;/title&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Discussion ===&lt;br /&gt;
&lt;br /&gt;
An HTTP message has a header and a body, which are sent to the client in that order. Once you begin sending the body, you can't send any more headers. So, if you call &amp;lt;tt&amp;gt;setcookie( )&amp;lt;/tt&amp;gt; after printing some HTML, PHP can't send the appropriate &amp;lt;tt&amp;gt;Cookie&amp;lt;/tt&amp;gt; header.&lt;br /&gt;
&lt;br /&gt;
Also, remove trailing whitespace in any include files. When you include a file with blank lines outside &amp;lt;tt&amp;gt;&amp;lt;?php ?&amp;gt;&amp;lt;/tt&amp;gt; tags, the blank lines are sent to the browser. Use &amp;lt;tt&amp;gt;trim( )&amp;lt;/tt&amp;gt; to remove leading and trailing blank lines from files:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;$file = '/path/to/file.php';&lt;br /&gt;
&lt;br /&gt;
// backup&lt;br /&gt;
copy($file, &amp;quot;$file.bak&amp;quot;) or die(&amp;quot;Can't copy $file: $php_errormsg);&lt;br /&gt;
&lt;br /&gt;
// read and trim&lt;br /&gt;
$contents = trim(join('',file($file)));&lt;br /&gt;
&lt;br /&gt;
// write&lt;br /&gt;
$fh = fopen($file, 'w')  or die(&amp;quot;Can't open $file for writing: $php_errormsg);&lt;br /&gt;
if (-1 == fwrite($fh, $contents)) { die(&amp;quot;Can't write to $file: $php_errormsg); }&lt;br /&gt;
fclose($fh)              or die(&amp;quot;Can't close $file: $php_errormsg);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Instead of processing files on a one-by-one basis, it may be more convenient to do so on a directory-by-directory basis. [[PHP Cookbook/Directories#Processing All Files in a Directory Recursively|Recipe 19.8]] describes how to process all the files in a directory.&lt;br /&gt;
&lt;br /&gt;
If you don't want to worry about blank lines disrupting the sending of headers, turn on output buffering. Output buffering prevents PHP from immediately sending all output to the client. If you buffer your output, you can intermix headers and body text with abandon. However, it may seem to users that your server takes longer to fulfill their requests since they have to wait slightly longer before the browser displays any output.&lt;br /&gt;
&lt;br /&gt;
=== See Also ===&lt;br /&gt;
&lt;br /&gt;
[[PHP Cookbook/Web Basics#Buffering Output to the Browser|Recipe 8.13]] discusses output buffering; [[PHP Cookbook/Directories#Processing All Files in a Directory Recursively|Recipe 19.8]] for processing all files in a directory; documentation on &amp;lt;tt&amp;gt;header( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/header''.&lt;br /&gt;
&lt;br /&gt;
== Logging Debugging Information ==&lt;br /&gt;
&lt;br /&gt;
=== Problem ===&lt;br /&gt;
&lt;br /&gt;
You want to make debugging easier by adding statements to print out variables. But, you want to easily be able to switch back and forth from production and debug modes.&lt;br /&gt;
&lt;br /&gt;
=== Solution ===&lt;br /&gt;
&lt;br /&gt;
Put a function that conditionally prints out messages based on a defined constant in a page included using the &amp;lt;tt&amp;gt;auto_prepend_file&amp;lt;/tt&amp;gt; configuration setting. Save the following code to ''debug.php'':&lt;br /&gt;
&lt;br /&gt;
 // turn debugging on&lt;br /&gt;
 define('DEBUG',true);&lt;br /&gt;
 &lt;br /&gt;
 // generic debugging function&lt;br /&gt;
 function pc_debug($message) {&lt;br /&gt;
     if (defined('DEBUG') &amp;amp;&amp;amp; DEBUG) {&lt;br /&gt;
         error_log($message);&lt;br /&gt;
     }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Set the &amp;lt;tt&amp;gt;auto_prepend_file&amp;lt;/tt&amp;gt; directive in ''php.ini'':&lt;br /&gt;
&lt;br /&gt;
 auto_prepend_file=debug.php&lt;br /&gt;
&lt;br /&gt;
Now call &amp;lt;tt&amp;gt;pc_debug( )&amp;lt;/tt&amp;gt; from your code to print out debugging information:&lt;br /&gt;
&lt;br /&gt;
 $sql = 'SELECT color, shape, smell FROM vegetables';&lt;br /&gt;
 pc_debug(&amp;quot;[sql: $sql]&amp;quot;); // only printed if DEBUG is true&lt;br /&gt;
 $r = mysql_query($sql);&lt;br /&gt;
&lt;br /&gt;
=== Discussion ===&lt;br /&gt;
&lt;br /&gt;
Debugging code is a necessary side-effect of writing code. There are a variety of techniques to help you quickly locate and squash your bugs. Many of these involve including scaffolding that helps ensure the correctness of your code. The more complicated the program, the more scaffolding needed. Fred Brooks, in ''The Mythical Man-Month'', guesses that there's &amp;quot;half as much code in scaffolding as there is in product.&amp;quot; Proper planning ahead of time allows you to integrate the scaffolding into your programming logic in a clean and efficient fashion. This requires you to think out beforehand what you want to measure and record and how you plan on sorting through the data gathered by your scaffolding.&lt;br /&gt;
&lt;br /&gt;
One technique for sifting through the information is to assign different priority levels to different types of debugging comments. Then the debug function prints information only if it's higher than the current priority level.&lt;br /&gt;
&lt;br /&gt;
 define('DEBUG',2);&lt;br /&gt;
 &lt;br /&gt;
 function pc_debug($message, $level = 0) {&lt;br /&gt;
     if (defined('DEBUG') &amp;amp;&amp;amp; ($level &amp;gt; DEBUG) {&lt;br /&gt;
         error_log($message);&lt;br /&gt;
     }&lt;br /&gt;
 }&lt;br /&gt;
 &lt;br /&gt;
 $sql = 'SELECT color, shape, smell FROM vegetables';&lt;br /&gt;
 pc_debug(&amp;quot;[sql: $sql]&amp;quot;, 1); // not printed, since 1 &amp;lt; 2&lt;br /&gt;
 pc_debug(&amp;quot;[sql: $sql]&amp;quot;, 3); // printed, since 3 &amp;gt; 2&lt;br /&gt;
&lt;br /&gt;
Another technique is to write wrapper functions to include additional information to help with performance tuning, such as the time it takes to execute a database query.&lt;br /&gt;
&lt;br /&gt;
 function getmicrotime(){&lt;br /&gt;
     $mtime = microtime();&lt;br /&gt;
     $mtime = explode(' ',$mtime);&lt;br /&gt;
     return ($mtime[1] + $mtime[0]);&lt;br /&gt;
 }&lt;br /&gt;
  &lt;br /&gt;
 function db_query($sql) {&lt;br /&gt;
     if (defined('DEBUG') &amp;amp;&amp;amp; DEBUG) {&lt;br /&gt;
          // start timing the query if DEBUG is on&lt;br /&gt;
          $DEBUG_STRING = &amp;quot;[sql: $sql]&amp;amp;lt;br&amp;gt;\n&amp;quot;;&lt;br /&gt;
          $starttime = getmicrotime();&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     $r = mysql_query($sql);&lt;br /&gt;
 &lt;br /&gt;
     if (! $r) {&lt;br /&gt;
         $error = mysql_error();&lt;br /&gt;
         error_log('[DB: query @'.$_SERVER['REQUEST_URI'].&amp;quot;][$sql]: $error&amp;quot;);&lt;br /&gt;
     } elseif (defined(DEBUG) &amp;amp;&amp;amp; DEBUG) {&lt;br /&gt;
         // the query didn't fail and DEBUG is turned on, so finish timing it&lt;br /&gt;
         $endtime = getmicrotime();&lt;br /&gt;
         $elapsedtime = $endtime - $starttime;&lt;br /&gt;
         $DEBUG_STRING .= &amp;quot;[time: $elapsedtime]&amp;amp;lt;br&amp;gt;\n&amp;quot;;&lt;br /&gt;
         error_log($DEBUG_STRING);&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     return $r;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Here, instead of just printing out the SQL to the error log, you also record the number of seconds it takes MySQL to perform the request. This lets you see if certain queries are taking too long.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;tt&amp;gt;getmicrotime( )&amp;lt;/tt&amp;gt; function converts the output of &amp;lt;tt&amp;gt;microtime( )&amp;lt;/tt&amp;gt; into a format that allows you to easily perform addition and subtraction upon the numbers.&lt;br /&gt;
&lt;br /&gt;
=== See Also ===&lt;br /&gt;
&lt;br /&gt;
Documentation on &amp;lt;tt&amp;gt;define( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/define'', &amp;lt;tt&amp;gt;defined( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/defined'', and &amp;lt;tt&amp;gt;error_log( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/error-log''; ''The Mythical Man-Month'', by Frederick P. Brooks (Addison-Wesley).&lt;br /&gt;
&lt;br /&gt;
== Reading Environment Variables ==&lt;br /&gt;
&lt;br /&gt;
=== Problem ===&lt;br /&gt;
&lt;br /&gt;
You want to get the value of an environment variable.&lt;br /&gt;
&lt;br /&gt;
=== Solution ===&lt;br /&gt;
&lt;br /&gt;
Read the value from the &amp;lt;tt&amp;gt;$_ENV&amp;lt;/tt&amp;gt; superglobal array:&lt;br /&gt;
&lt;br /&gt;
 $name = $_ENV['USER'];&lt;br /&gt;
&lt;br /&gt;
=== Discussion ===&lt;br /&gt;
&lt;br /&gt;
Environment variables are named values associated with a process. For instance, in Unix, you can check the value of &amp;lt;tt&amp;gt;$_ENV['HOME']&amp;lt;/tt&amp;gt; to find the home directory of a user:&lt;br /&gt;
&lt;br /&gt;
 print $_ENV['HOME']; // user's home directory&lt;br /&gt;
 '''/home/adam'''&lt;br /&gt;
             &lt;br /&gt;
&lt;br /&gt;
Early versions of PHP automatically created PHP variables for all environment variables by default. As of 4.1.0, ''php.ini-recommended'' disables this because of speed considerations; however ''php.ini-dist'' continues to enable environment variable loading for backward compatibility.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;tt&amp;gt;$_ENV&amp;lt;/tt&amp;gt; array is created only if the value of the &amp;lt;tt&amp;gt;variables_order&amp;lt;/tt&amp;gt; configuration directive contains &amp;lt;tt&amp;gt;E&amp;lt;/tt&amp;gt;. If &amp;lt;tt&amp;gt;$_ENV&amp;lt;/tt&amp;gt; isn't available, use &amp;lt;tt&amp;gt;getenv( )&amp;lt;/tt&amp;gt; to retrieve an environment variable:&lt;br /&gt;
&lt;br /&gt;
 $path = getenv('PATH');&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;tt&amp;gt;getenv( )&amp;lt;/tt&amp;gt; function isn't available if you're running PHP as an ISAPI module.&lt;br /&gt;
&lt;br /&gt;
=== See Also ===&lt;br /&gt;
&lt;br /&gt;
[[PHP Cookbook/Web Basics#Setting Environment Variables|Recipe 8.22]] on setting environment variables; documentation on &amp;lt;tt&amp;gt;getenv( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/getenv''; information on environment variables in PHP at ''http://www.php.net/reserved.variables.php#reserved.variables.environment''.&lt;br /&gt;
&lt;br /&gt;
== Setting Environment Variables ==&lt;br /&gt;
&lt;br /&gt;
=== Problem ===&lt;br /&gt;
&lt;br /&gt;
You want to set an environment variable in a script or in your server configuration. Setting environment variables in your server configuration on a host-by-host basis allows you to configure virtual hosts differently.&lt;br /&gt;
&lt;br /&gt;
=== Solution ===&lt;br /&gt;
&lt;br /&gt;
To set an environment variable in a script, use &amp;lt;tt&amp;gt;putenv( )&amp;lt;/tt&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
 putenv('ORACLE_SID=ORACLE'); // configure oci extension&lt;br /&gt;
&lt;br /&gt;
To set an environment variable in your Apache ''httpd.conf'' file, use &amp;lt;tt&amp;gt;SetEnv&amp;lt;/tt&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
 SetEnv DATABASE_PASSWORD password&lt;br /&gt;
&lt;br /&gt;
=== Discussion ===&lt;br /&gt;
&lt;br /&gt;
An advantage of setting variables in ''httpd.conf'' is that you can set more restrictive read permissions on it than on your PHP scripts. Since PHP files need to be readable by the web-server process, this generally allows other users on the system to view them. By storing passwords in ''httpd.conf'', you can avoid placing a password in a publicly available file. Also, if you have multiple hostnames that map to the same document root, you can configure your scripts to behave differently based on the hostnames.&lt;br /&gt;
&lt;br /&gt;
For example, you could have ''members.example.com'' and ''guests.example.com''. The members version requires authentication and allows users additional access. The guests version provides a restricted set of options, but without authentication:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;$version = $_ENV['SITE_VERSION'];&lt;br /&gt;
&lt;br /&gt;
// redirect to http://guest.example.com, if user fails to sign in correctly&lt;br /&gt;
if ('members' == $version) {&lt;br /&gt;
    if (!authenticate_user($_REQUEST['username'], $_REQUEST['password'])) {&lt;br /&gt;
        header('Location: http://guest.example.com/');&lt;br /&gt;
        exit;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
include_once &amp;quot;${version}_header&amp;quot;; // load custom header&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== See Also ===&lt;br /&gt;
&lt;br /&gt;
[[PHP Cookbook/Web Basics#Reading Environment Variables|Recipe 8.21]] on getting the values of environment variables; documentation on &amp;lt;tt&amp;gt;putenv( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/putenv''; information on setting environment variables in Apache at ''http://httpd.apache.org/docs/mod/mod_env.html''.&lt;br /&gt;
&lt;br /&gt;
== Reading Configuration Variables ==&lt;br /&gt;
&lt;br /&gt;
=== Problem ===&lt;br /&gt;
&lt;br /&gt;
You want to get the value of a PHP configuration setting.&lt;br /&gt;
&lt;br /&gt;
=== Solution ===&lt;br /&gt;
&lt;br /&gt;
Use &amp;lt;tt&amp;gt;ini_get( )&amp;lt;/tt&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
 // find out the include path:&lt;br /&gt;
 $include_path = ini_get('include_path');&lt;br /&gt;
&lt;br /&gt;
=== Discussion ===&lt;br /&gt;
&lt;br /&gt;
To get all configuration variable values in one step, call &amp;lt;tt&amp;gt;ini_get_all( )&amp;lt;/tt&amp;gt; . It returns the variables in an associative array, and each array element is itself an associative array. The second array has three elements: a global value for the setting, a local value, and an access code:&lt;br /&gt;
&lt;br /&gt;
 // put all configuration variables in an associative array&lt;br /&gt;
 $vars = ini_get_all( );&lt;br /&gt;
 print_r($vars['include_path']);&lt;br /&gt;
 '''Array'''&lt;br /&gt;
                '''('''&lt;br /&gt;
                '''    [global_value] =&amp;gt; .:/usr/local/lib/php/'''&lt;br /&gt;
                '''    [local_value] =&amp;gt; .:/usr/local/lib/php/'''&lt;br /&gt;
                '''    [access] =&amp;gt; 7'''&lt;br /&gt;
                ''')'''&lt;br /&gt;
             &lt;br /&gt;
&lt;br /&gt;
The &amp;lt;tt&amp;gt;global_value&amp;lt;/tt&amp;gt; is the value set from the ''php.ini'' file; the &amp;lt;tt&amp;gt;local_value&amp;lt;/tt&amp;gt; is adjusted to account for any changes made in the web server's configuration file, any relevant ''.htaccess'' files, and the current script. The value of &amp;lt;tt&amp;gt;access&amp;lt;/tt&amp;gt; is a numeric constant representing the places where this value can be altered. [[PHP Cookbook/Web Basics#phpckbk-CHP-8-TABLE-3|Table 8-3]] explains the values for &amp;lt;tt&amp;gt;access&amp;lt;/tt&amp;gt;. Note that the name &amp;lt;tt&amp;gt;access&amp;lt;/tt&amp;gt; is a little misleading in this respect, as the setting's value can always be checked, but not adjusted.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;phpckbk-CHP-8-TABLE-3&amp;quot;&amp;gt;&lt;br /&gt;
'''Table 8-3. Access values'''&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; &lt;br /&gt;
|-&lt;br /&gt;
! Value !! PHP constant !! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;1&amp;lt;/tt&amp;gt; || &amp;lt;tt&amp;gt;PHP_INI_USER&amp;lt;/tt&amp;gt; || Any script, using &amp;lt;tt&amp;gt;ini_set( )&amp;lt;/tt&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;2&amp;lt;/tt&amp;gt; || &amp;lt;tt&amp;gt;PHP_INI_PERDIR&amp;lt;/tt&amp;gt; || Directory level, using ''.htaccess''&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;4&amp;lt;/tt&amp;gt; || &amp;lt;tt&amp;gt;PHP_INI_SYSTEM&amp;lt;/tt&amp;gt; || System level, using ''php.ini'' or ''httpd.conf''&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;7&amp;lt;/tt&amp;gt; || &amp;lt;tt&amp;gt;PHP_INI_ALL&amp;lt;/tt&amp;gt; || Everywhere: scripts, directories, and the system&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
A value of &amp;lt;tt&amp;gt;6&amp;lt;/tt&amp;gt; means the setting can be changed in both the directory and system level, as 2 + 4 = 6. In practice, there are no variables modifiable only in &amp;lt;tt&amp;gt;PHP_INI_USER&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;PHP_INI_PERDIR&amp;lt;/tt&amp;gt;, and all variables are modifiable in &amp;lt;tt&amp;gt;PHP_INI_SYSTEM&amp;lt;/tt&amp;gt;, so everything has a value of &amp;lt;tt&amp;gt;4&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;6&amp;lt;/tt&amp;gt;, or &amp;lt;tt&amp;gt;7&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
You can also get variables belonging to a specific extension by passing the extension name to &amp;lt;tt&amp;gt;ini_get_all( )&amp;lt;/tt&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
 // return just the session module specific variables&lt;br /&gt;
 $session = ini_get_all('session');&lt;br /&gt;
&lt;br /&gt;
By convention, the variables for an extension are prefixed with the extension name and a period. So, all the session variables begin with &amp;lt;tt&amp;gt;session&amp;lt;/tt&amp;gt;. and all the Java variables begin with &amp;lt;tt&amp;gt;java&amp;lt;/tt&amp;gt;., for example.&lt;br /&gt;
&lt;br /&gt;
Since &amp;lt;tt&amp;gt;ini_get( )&amp;lt;/tt&amp;gt; returns the current value for a configuration directive, if you want to check the original value from the ''php.ini'' file, use &amp;lt;tt&amp;gt;get_cfg_var( )&amp;lt;/tt&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
 $original = get_cfg_var('sendmail_from'); // have we changed our address?&lt;br /&gt;
&lt;br /&gt;
The value returned by &amp;lt;tt&amp;gt;get_cfg_var( )&amp;lt;/tt&amp;gt; is the same as what appears in the &amp;lt;tt&amp;gt;global_value&amp;lt;/tt&amp;gt; element of the array returned by &amp;lt;tt&amp;gt;ini_get_all( )&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== See Also ===&lt;br /&gt;
&lt;br /&gt;
[[PHP Cookbook/Web Basics#Setting Configuration Variables|Recipe 8.24]] on setting configuration variables; documentation on &amp;lt;tt&amp;gt;ini_get( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/ini-get'', &amp;lt;tt&amp;gt;ini_get_all( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/ini-get-all'', and &amp;lt;tt&amp;gt;get_cfg_var( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/get-cfg-var''; a complete list of configuration variables and when they can be modified at ''http://www.php.net/function.ini-set.php''.&lt;br /&gt;
&lt;br /&gt;
== Setting Configuration Variables ==&lt;br /&gt;
&lt;br /&gt;
=== Problem ===&lt;br /&gt;
&lt;br /&gt;
You want to change the value of a PHP configuration setting.&lt;br /&gt;
&lt;br /&gt;
=== Solution ===&lt;br /&gt;
&lt;br /&gt;
Use &amp;lt;tt&amp;gt;ini_set( )&amp;lt;/tt&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
 // add a directory to the include path&lt;br /&gt;
 ini_set('include_path', ini_get('include_path') . ':/home/fezzik/php');&lt;br /&gt;
&lt;br /&gt;
=== Discussion ===&lt;br /&gt;
&lt;br /&gt;
Configuration variables are not permanently changed by &amp;lt;tt&amp;gt;ini_set( )&amp;lt;/tt&amp;gt;. The new value lasts only for the duration of the request in which &amp;lt;tt&amp;gt;ini_set( )&amp;lt;/tt&amp;gt; is called. To make a persistent modification, alter the values stored in the ''php.ini'' file.&lt;br /&gt;
&lt;br /&gt;
It isn't meaningful to alter certain variables, such as &amp;lt;tt&amp;gt;asp_tags&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;register_globals&amp;lt;/tt&amp;gt; because by the time you call &amp;lt;tt&amp;gt;ini_set( )&amp;lt;/tt&amp;gt; to modify the setting, it's too late to change the behavior the setting affects. If a variable can't be changed, &amp;lt;tt&amp;gt;ini_set( )&amp;lt;/tt&amp;gt; returns &amp;lt;tt&amp;gt;false&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
However, it is useful to alter configuration variables in certain pages. For example, if you're running a script from the command line, set &amp;lt;tt&amp;gt;html_errors&amp;lt;/tt&amp;gt; to &amp;lt;tt&amp;gt;off&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
To reset a variable back to its original setting, use &amp;lt;tt&amp;gt;ini_restore( )&amp;lt;/tt&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
 ini_restore('sendmail_from'); // go back to the default value&lt;br /&gt;
&lt;br /&gt;
=== See Also ===&lt;br /&gt;
&lt;br /&gt;
[[PHP Cookbook/Web Basics#Reading Configuration Variables|Recipe 8.23]] on getting values of configuration variables; documentation on &amp;lt;tt&amp;gt;ini_set( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/ini-set'' and &amp;lt;tt&amp;gt;ini_restore( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/ini-restore''.&lt;br /&gt;
&lt;br /&gt;
== Communicating Within Apache ==&lt;br /&gt;
&lt;br /&gt;
=== Problem ===&lt;br /&gt;
&lt;br /&gt;
You want to communicate from PHP to other parts of the Apache request process. This includes setting variables in the ''access_log'' .&lt;br /&gt;
&lt;br /&gt;
=== Solution ===&lt;br /&gt;
&lt;br /&gt;
Use &amp;lt;tt&amp;gt;apache_note( )&amp;lt;/tt&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
 // get value&lt;br /&gt;
 $session = apache_note('session');&lt;br /&gt;
 &lt;br /&gt;
 // set value&lt;br /&gt;
 apache_note('session', $session);&lt;br /&gt;
&lt;br /&gt;
=== Discussion ===&lt;br /&gt;
&lt;br /&gt;
When Apache processes a request from a client, it goes through a series of steps; PHP plays only one part in the entire chain. Apache also remaps URLs, authenticates users, logs requests, and more. While processing a request, each handler has access to a set of key/value pairs called the ''notes table''. The &amp;lt;tt&amp;gt;apache_note( )&amp;lt;/tt&amp;gt; function provides access to the notes table to retrieve information set by handlers earlier on in the process and leave information for handlers later on.&lt;br /&gt;
&lt;br /&gt;
For example, if you use the session module to track users and preserve variables across requests, you can integrate this with your log file analysis so you can determine the average number of page views per user. Use &amp;lt;tt&amp;gt;apache_note( )&amp;lt;/tt&amp;gt; in combination with the logging module to write the session ID directly to the ''access_log'' for each request:&lt;br /&gt;
&lt;br /&gt;
 // retrieve the session ID and add it to Apache's notes table&lt;br /&gt;
 apache_note('session_id', session_id( ));&lt;br /&gt;
&lt;br /&gt;
Then, modify your ''httpd.conf'' file to add this string to your &amp;lt;tt&amp;gt;LogFormat&amp;lt;/tt&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
 %{session_id}n&lt;br /&gt;
&lt;br /&gt;
The trailing &amp;lt;tt&amp;gt;n&amp;lt;/tt&amp;gt; tells Apache to use a variable stored in its notes table by another module.&lt;br /&gt;
&lt;br /&gt;
If PHP is built with the &amp;lt;tt&amp;gt;--enable-memory-limit&amp;lt;/tt&amp;gt; configuration option, it stores the peak memory usage of each request in a note called &amp;lt;tt&amp;gt;mod_php_memory_usage&amp;lt;/tt&amp;gt;. Add the memory usage information to a &amp;lt;tt&amp;gt;LogFormat&amp;lt;/tt&amp;gt; with:&lt;br /&gt;
&lt;br /&gt;
 %{mod_php_memory_usage}n&lt;br /&gt;
&lt;br /&gt;
=== See Also ===&lt;br /&gt;
&lt;br /&gt;
Documentation on &amp;lt;tt&amp;gt;apache_note( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/apache-note''; information on logging in Apache at ''http://httpd.apache.org/docs/mod/mod_log_config.html''.&lt;br /&gt;
&lt;br /&gt;
== Profiling Code ==&lt;br /&gt;
&lt;br /&gt;
=== Problem ===&lt;br /&gt;
&lt;br /&gt;
You have a block of code and you want to profile it to see how long each statement takes to execute.&lt;br /&gt;
&lt;br /&gt;
=== Solution ===&lt;br /&gt;
&lt;br /&gt;
Use the PEAR Benchmark module:&lt;br /&gt;
&lt;br /&gt;
 require 'Benchmark/Timer.php';&lt;br /&gt;
 &lt;br /&gt;
 $timer =&amp;amp; new Benchmark_Timer(true);&lt;br /&gt;
 &lt;br /&gt;
 $timer-&amp;gt;start();&lt;br /&gt;
 // some setup code here&lt;br /&gt;
 $timer-&amp;gt;setMarker('setup');&lt;br /&gt;
 // some more code executed here&lt;br /&gt;
 $timer-&amp;gt;setMarker('middle');&lt;br /&gt;
 // even yet still more code here&lt;br /&gt;
 $timer-&amp;gt;setmarker('done');&lt;br /&gt;
 // and a last bit of code here&lt;br /&gt;
 $timer-&amp;gt;stop();&lt;br /&gt;
 &lt;br /&gt;
 $timer-&amp;gt;display();&lt;br /&gt;
&lt;br /&gt;
=== Discussion ===&lt;br /&gt;
&lt;br /&gt;
Calling &amp;lt;tt&amp;gt;setMarker( )&amp;lt;/tt&amp;gt; records the time. The &amp;lt;tt&amp;gt;display( )&amp;lt;/tt&amp;gt; method prints out a list of markers, the time they were set, and the elapsed time from the previous marker:&lt;br /&gt;
&lt;br /&gt;
 -------------------------------------------------------------&lt;br /&gt;
 marker    time index            ex time               perct&lt;br /&gt;
 -------------------------------------------------------------&lt;br /&gt;
 Start     1029433375.42507400   -                       0.00%&lt;br /&gt;
 -------------------------------------------------------------&lt;br /&gt;
 setup     1029433375.42554800   0.00047397613525391    29.77%&lt;br /&gt;
 -------------------------------------------------------------&lt;br /&gt;
 middle    1029433375.42568700   0.00013899803161621     8.73%&lt;br /&gt;
 -------------------------------------------------------------&lt;br /&gt;
 done      1029433375.42582000   0.00013303756713867     8.36%&lt;br /&gt;
 -------------------------------------------------------------&lt;br /&gt;
 Stop      1029433375.42666600   0.00084602832794189    53.14%&lt;br /&gt;
 -------------------------------------------------------------&lt;br /&gt;
 total     -                     0.0015920400619507    100.00%&lt;br /&gt;
 -------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
The Benchmark module also includes the &amp;lt;tt&amp;gt;Benchmark_Iterate&amp;lt;/tt&amp;gt; class, which can be used to time many executions of a single function:&lt;br /&gt;
&lt;br /&gt;
 require 'Benchmark/Iterate.php';&lt;br /&gt;
 &lt;br /&gt;
 $timer =&amp;amp; new Benchmark_Iterate;&lt;br /&gt;
 &lt;br /&gt;
 // a sample function to time&lt;br /&gt;
 function use_preg($ar) {&lt;br /&gt;
     for ($i = 0, $j = count($ar); $i &amp;lt; $j; $i++) {&lt;br /&gt;
         if (preg_match('/gouda/',$ar[$i])) {&lt;br /&gt;
             // it's gouda&lt;br /&gt;
         }&lt;br /&gt;
     }&lt;br /&gt;
 }&lt;br /&gt;
 &lt;br /&gt;
 // another sample function to time&lt;br /&gt;
 function use_equals($ar) {&lt;br /&gt;
     for ($i = 0, $j = count($ar); $i &amp;lt; $j; $i++) {&lt;br /&gt;
         if ('gouda' == $ar[$i]) {&lt;br /&gt;
             // it's gouda&lt;br /&gt;
         }&lt;br /&gt;
     }&lt;br /&gt;
 }&lt;br /&gt;
 &lt;br /&gt;
 // run use_preg() 1000 times&lt;br /&gt;
 $timer-&amp;gt;run(1000,'use_preg',&lt;br /&gt;
                 array('gouda','swiss','gruyere','muenster','whiz'));&lt;br /&gt;
 $results = $timer-&amp;gt;get();&lt;br /&gt;
 print &amp;quot;Mean execution time for use_preg(): $results[mean]\n&amp;quot;;&lt;br /&gt;
 &lt;br /&gt;
 // run use_equals() 1000 times&lt;br /&gt;
 $timer-&amp;gt;run(1000,'use_equals',&lt;br /&gt;
                 array('gouda','swiss','gruyere','muenster','whiz'));&lt;br /&gt;
 $results = $timer-&amp;gt;get();&lt;br /&gt;
 print &amp;quot;Mean execution time for use_equals(): $results[mean]\n&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;tt&amp;gt;Benchmark_Iterate::get( )&amp;lt;/tt&amp;gt; method returns an associative array. The &amp;lt;tt&amp;gt;mean&amp;lt;/tt&amp;gt; element of this array holds the mean execution time for each iteration of the function. The &amp;lt;tt&amp;gt;iterations&amp;lt;/tt&amp;gt; element holds the number of iterations. The execution time of each iteration of the function is stored in an array element with an integer key. For example, the time of the first iteration is in &amp;lt;tt&amp;gt;$results[1]&amp;lt;/tt&amp;gt;, and the time of the 37th iteration is in &amp;lt;tt&amp;gt;$results[37]&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
To automatically record the elapsed execution time after every line of PHP code, use the &amp;lt;tt&amp;gt;declare&amp;lt;/tt&amp;gt; construct and the &amp;lt;tt&amp;gt;ticks&amp;lt;/tt&amp;gt; directive:&lt;br /&gt;
&lt;br /&gt;
 function profile($display = false) {&lt;br /&gt;
     static $times;&lt;br /&gt;
 &lt;br /&gt;
     switch ($display) {&lt;br /&gt;
     case false:&lt;br /&gt;
         // add the current time to the list of recorded times&lt;br /&gt;
         $times[] = microtime();&lt;br /&gt;
         break;&lt;br /&gt;
     case true:&lt;br /&gt;
         // return elapsed times in microseconds&lt;br /&gt;
         $start = array_shift($times);&lt;br /&gt;
 &lt;br /&gt;
         $start_mt = explode(' ', $start); &lt;br /&gt;
         $start_total = doubleval($start_mt[0]) + $start_mt[1]; &lt;br /&gt;
 &lt;br /&gt;
         foreach ($times as $stop) { &lt;br /&gt;
             $stop_mt = explode(' ', $stop); &lt;br /&gt;
             $stop_total = doubleval($stop_mt[0]) + $stop_mt[1]; &lt;br /&gt;
             $elapsed[] = $stop_total - $start_total; &lt;br /&gt;
         }&lt;br /&gt;
 &lt;br /&gt;
         unset($times);&lt;br /&gt;
         return $elapsed;&lt;br /&gt;
         break;&lt;br /&gt;
     }&lt;br /&gt;
 }&lt;br /&gt;
 &lt;br /&gt;
 // register tick handler&lt;br /&gt;
 register_tick_function('profile');&lt;br /&gt;
 &lt;br /&gt;
 // clock the start time&lt;br /&gt;
 profile();&lt;br /&gt;
 &lt;br /&gt;
 // execute code, recording time for every statement execution&lt;br /&gt;
 declare (ticks = 1) {&lt;br /&gt;
     foreach ($_SERVER['argv'] as $arg) {&lt;br /&gt;
         print strlen($arg);&lt;br /&gt;
     }&lt;br /&gt;
 }&lt;br /&gt;
 &lt;br /&gt;
 // print out elapsed times&lt;br /&gt;
 $i = 0;&lt;br /&gt;
 foreach (profile(true) as $time) {&lt;br /&gt;
     $i++;&lt;br /&gt;
     print &amp;quot;Line $i: $time\n&amp;quot;;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;tt&amp;gt;ticks&amp;lt;/tt&amp;gt; directive allows you to execute a function on a repeatable basis for a block of code. The number assigned to &amp;lt;tt&amp;gt;ticks&amp;lt;/tt&amp;gt; is how many statements go by before the functions that are registered using &amp;lt;tt&amp;gt;register_tick_function( )&amp;lt;/tt&amp;gt; are executed.&lt;br /&gt;
&lt;br /&gt;
In the previous example, we register a single function and have the &amp;lt;tt&amp;gt;profile( )&amp;lt;/tt&amp;gt; function execute for every statement inside the &amp;lt;tt&amp;gt;declare&amp;lt;/tt&amp;gt; block. If there are two elements in &amp;lt;tt&amp;gt;$_SERVER['argv']&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;profile( )&amp;lt;/tt&amp;gt; is executed four times: once for each time through the &amp;lt;tt&amp;gt;foreach&amp;lt;/tt&amp;gt; loop, and once each time the &amp;lt;tt&amp;gt;print strlen($arg)&amp;lt;/tt&amp;gt; line is executed.&lt;br /&gt;
&lt;br /&gt;
You can also set things up to call two functions every three statements:&lt;br /&gt;
&lt;br /&gt;
 register_tick_function('profile');&lt;br /&gt;
 register_tick_function('backup');&lt;br /&gt;
 &lt;br /&gt;
 declare (ticks = 3) {&lt;br /&gt;
     // code...&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
You can also pass additional parameters into the registered functions, which can be object methods instead of regular functions:&lt;br /&gt;
&lt;br /&gt;
 // pass &amp;quot;parameter&amp;quot; into profile( )&lt;br /&gt;
 register_tick_function('profile', 'parameter');&lt;br /&gt;
 &lt;br /&gt;
 // call $car-&amp;gt;drive( );&lt;br /&gt;
 $car = new Vehicle;&lt;br /&gt;
 register_tick_function(array($car, 'drive'));&lt;br /&gt;
&lt;br /&gt;
If you want to execute an object method, pass the object and the name of the method in encapsulated within an array. This lets the &amp;lt;tt&amp;gt;register_tick_function( )&amp;lt;/tt&amp;gt; know you're referring to an object instead of a function.&lt;br /&gt;
&lt;br /&gt;
Call &amp;lt;tt&amp;gt;unregister_tick_function( )&amp;lt;/tt&amp;gt; to remove a function from the list of tick functions:&lt;br /&gt;
&lt;br /&gt;
 unregister_tick_function('profile');&lt;br /&gt;
&lt;br /&gt;
=== See Also ===&lt;br /&gt;
&lt;br /&gt;
''http://pear.php.net/package-info.php?package=Benchmark'' for information on the PEAR Benchmark class; documentation on &amp;lt;tt&amp;gt;register_tick_function( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/register-tick-function'', &amp;lt;tt&amp;gt;unregister_tick_function( )&amp;lt;/tt&amp;gt; at ''http://www.php.net/unregister-tick-function,anddeclareathttp://www.php.net/declare''.&lt;br /&gt;
&lt;br /&gt;
== Program: Website Account (De)activator ==&lt;br /&gt;
&lt;br /&gt;
When users sign up for your web site, it's helpful to know that they've provided you with a correct email address. To validate the email address they provide, send an email to the address they supply when they sign up. If they don't visit a special URL included in the email after a few days, deactivate their account.&lt;br /&gt;
&lt;br /&gt;
This system has three parts. The first is the ''notify-user.php'' program that sends an email to a new user and asks them to visit a verification URL, shown in [[PHP Cookbook/Web Basics#phpckbk-CHP-8-EX-4|Example 8-4]]. The second, shown in [[PHP Cookbook/Web Basics#phpckbk-CHP-8-EX-5|Example 8-5]], is the ''verify-user.php'' page that handles the verification URL and marks users as valid. The third is the ''delete-user.php'' program that deactivates accounts of users who don't visit the verification URL after a certain amount of time. This program is shown in [[PHP Cookbook/Web Basics#phpckbk-CHP-8-EX-6|Example 8-6]].&lt;br /&gt;
&lt;br /&gt;
Here's the SQL to create the table that user information is stored in:&lt;br /&gt;
&lt;br /&gt;
 CREATE TABLE users (&lt;br /&gt;
  email VARCHAR(255) NOT NULL,&lt;br /&gt;
  created_on DATETIME NOT NULL,&lt;br /&gt;
  verify_string VARCHAR(16) NOT NULL,&lt;br /&gt;
  verified TINYINT UNSIGNED&lt;br /&gt;
 );&lt;br /&gt;
&lt;br /&gt;
You probably want to store more information than this about your users, but this is all that's needed to verify them. When creating a user's account, save information to the &amp;lt;tt&amp;gt;users&amp;lt;/tt&amp;gt; table, and send the user an email telling them how to verify their account. The code in [[PHP Cookbook/Web Basics#phpckbk-CHP-8-EX-4|Example 8-4]] assumes that user's email address is stored in the variable &amp;lt;tt&amp;gt;$email&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;phpckbk-CHP-8-EX-4&amp;quot;&amp;gt;&lt;br /&gt;
'''Example 8-4. notify-user.php'''&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;// generate verify_string&lt;br /&gt;
$verify_string = '';&lt;br /&gt;
for ($i = 0; $i &amp;lt; 16; $i++) {&lt;br /&gt;
    $verify_string .= chr(mt_rand(32,126));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// insert user into database&lt;br /&gt;
if (! mysql_query(&amp;quot;INSERT INTO users (email,created_on,verify_string,verified)&lt;br /&gt;
    VALUES ('&amp;quot;.addslashes($email).&amp;quot;',NOW(),'&amp;quot;.addslashes($verify_string).&amp;quot;',0)&amp;quot;)) {&lt;br /&gt;
    error_log(&amp;quot;Can't insert user: &amp;quot;.mysql_error());&lt;br /&gt;
    exit;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$verify_string = urlencode($verify_string);&lt;br /&gt;
$safe_email = urlencode($email);&lt;br /&gt;
&lt;br /&gt;
$verify_url = &amp;quot;http://www.example.com/verify.php&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
$mail_body=&amp;lt;&amp;lt;&amp;lt;_MAIL_&lt;br /&gt;
To $email:&lt;br /&gt;
&lt;br /&gt;
Please click on the following link to verify your account creation:&lt;br /&gt;
&lt;br /&gt;
$verify_url?email=$safe_email&amp;amp;verify_string=$verify_string&lt;br /&gt;
&lt;br /&gt;
If you do not verify your account in the next seven days, it will be&lt;br /&gt;
deleted.&lt;br /&gt;
_MAIL_;&lt;br /&gt;
&lt;br /&gt;
mail($email,&amp;quot;User Verification&amp;quot;,$mail_body);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The verification page users go to when they follow the link in the email message updates the &amp;lt;tt&amp;gt;users&amp;lt;/tt&amp;gt; table if the proper information has been provided, as shown in [[PHP Cookbook/Web Basics#phpckbk-CHP-8-EX-5|Example 8-5]].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;phpckbk-CHP-8-EX-5&amp;quot;&amp;gt;&lt;br /&gt;
'''Example 8-5. verify-user.php'''&lt;br /&gt;
&lt;br /&gt;
 $safe_email = addslashes($_REQUEST['email']);&lt;br /&gt;
 $safe_verify_string = addslashes($_REQUEST['verify_string']);&lt;br /&gt;
 &lt;br /&gt;
 if ($r = mysql_query(&amp;quot;UPDATE users SET verified = 1 WHERE email &lt;br /&gt;
     LIKE '$safe_email' AND &lt;br /&gt;
     verify_string = '$safe_verify_string' AND verified = 0&amp;quot;)) {&lt;br /&gt;
     if (mysql_affected_rows() == 1) {&lt;br /&gt;
         print &amp;quot;Thank you, your account is verified.&amp;quot;;&lt;br /&gt;
     } else {&lt;br /&gt;
         print &amp;quot;Sorry, you could not be verified.&amp;quot;;&lt;br /&gt;
     }&lt;br /&gt;
 } else {&lt;br /&gt;
     print &amp;quot;Please try again later due to a database error.&amp;quot;;&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The user's verification status is updated only if the email address and verify string provided match a row in the database that has not already been verified. The last step is the short program that deletes unverified users after the appropriate interval, as shown in [[PHP Cookbook/Web Basics#phpckbk-CHP-8-EX-6|Example 8-6]].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;phpckbk-CHP-8-EX-6&amp;quot;&amp;gt;&lt;br /&gt;
'''Example 8-6. delete-user.php'''&lt;br /&gt;
&lt;br /&gt;
 $window = 7; // in days &lt;br /&gt;
 &lt;br /&gt;
 if ($r = mysql_query(&amp;quot;DELETE FROM users WHERE verified = 0 AND &lt;br /&gt;
     created_on &amp;lt; DATE_SUB(NOW(),INTERVAL $window DAY)&amp;quot;)) {&lt;br /&gt;
     if ($deleted_users = mysql_affected_rows()) {&lt;br /&gt;
         print &amp;quot;Deactivated $deleted_users users.\n&amp;quot;;&lt;br /&gt;
     }&lt;br /&gt;
 } else {&lt;br /&gt;
     print &amp;quot;Can't delete users: &amp;quot;.mysql_error();&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Run this program once a day to scrub the &amp;lt;tt&amp;gt;users&amp;lt;/tt&amp;gt; table of users that haven't been verified. If you want to change how long users have to verify themselves, adjust the value of &amp;lt;tt&amp;gt;$window&amp;lt;/tt&amp;gt;, and update the text of the email message sent to users to reflect the new value.&lt;br /&gt;
&lt;br /&gt;
== Program: Abusive User Checker ==&lt;br /&gt;
&lt;br /&gt;
Shared memory's speed makes it an ideal way to store data different web server processes need to access frequently when a file or database would be too slow. [[PHP Cookbook/Web Basics#phpckbk-CHP-8-EX-7|Example 8-7]] shows the &amp;lt;tt&amp;gt;pc_Web_Abuse_Check&amp;lt;/tt&amp;gt; class, which uses shared memory to track accesses to web pages in order to cut off users that abuse a site by bombarding it with requests.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;phpckbk-CHP-8-EX-7&amp;quot;&amp;gt;&lt;br /&gt;
'''Example 8-7. pc_Web_Abuse_Check class'''&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;class pc_Web_Abuse_Check {&lt;br /&gt;
  var $sem_key;&lt;br /&gt;
  var $shm_key;&lt;br /&gt;
  var $shm_size;&lt;br /&gt;
  var $recalc_seconds;&lt;br /&gt;
  var $pageview_threshold;&lt;br /&gt;
  var $sem;&lt;br /&gt;
  var $shm;&lt;br /&gt;
  var $data;&lt;br /&gt;
  var $exclude;&lt;br /&gt;
  var $block_message;&lt;br /&gt;
&lt;br /&gt;
  function pc_Web_Abuse_Check() {&lt;br /&gt;
    $this-&amp;gt;sem_key = 5000;&lt;br /&gt;
    $this-&amp;gt;shm_key = 5001;&lt;br /&gt;
    $this-&amp;gt;shm_size = 16000;&lt;br /&gt;
    $this-&amp;gt;recalc_seconds = 60;&lt;br /&gt;
    $this-&amp;gt;pageview_threshold = 30;&lt;br /&gt;
&lt;br /&gt;
    $this-&amp;gt;exclude['/ok-to-bombard.html'] = 1;&lt;br /&gt;
    $this-&amp;gt;block_message =&amp;lt;&amp;lt;&amp;lt;END&lt;br /&gt;
&amp;lt;html&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&amp;lt;title&amp;gt;403 Forbidden&amp;lt;/title&amp;gt;&amp;lt;/head&amp;gt;&lt;br /&gt;
&amp;lt;body&amp;gt;&lt;br /&gt;
&amp;lt;h1&amp;gt;Forbidden&amp;lt;/h1&amp;gt;&lt;br /&gt;
You have been blocked from retrieving pages from this site due to&lt;br /&gt;
abusive repetitive activity from your account. If you believe this&lt;br /&gt;
is an error, please contact &lt;br /&gt;
&amp;lt;a href=&amp;quot;mailto:webmaster@example.com?subject=Site+Abuse&amp;quot;&amp;gt;webmaster@example.com&amp;lt;/a&amp;gt;.&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
END;&lt;br /&gt;
   }&lt;br /&gt;
  &lt;br /&gt;
  function get_lock() {&lt;br /&gt;
    $this-&amp;gt;sem = sem_get($this-&amp;gt;sem_key,1,0600);&lt;br /&gt;
    if (sem_acquire($this-&amp;gt;sem)) {&lt;br /&gt;
      $this-&amp;gt;shm = shm_attach($this-&amp;gt;shm_key,$this-&amp;gt;shm_size,0600);&lt;br /&gt;
      $this-&amp;gt;data = shm_get_var($this-&amp;gt;shm,'data');&lt;br /&gt;
    } else {&lt;br /&gt;
      error_log(&amp;quot;Can't acquire semaphore $this-&amp;gt;sem_key&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function release_lock() {&lt;br /&gt;
    if (isset($this-&amp;gt;data)) {&lt;br /&gt;
      shm_put_var($this-&amp;gt;shm,'data',$this-&amp;gt;data);&lt;br /&gt;
    }&lt;br /&gt;
    shm_detach($this-&amp;gt;shm);&lt;br /&gt;
    sem_release($this-&amp;gt;sem);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function check_abuse($user) {&lt;br /&gt;
    $this-&amp;gt;get_lock();&lt;br /&gt;
    if ($this-&amp;gt;data['abusive_users'][$user]) {&lt;br /&gt;
      // if user is on the list release the semaphore &amp;amp; memory &lt;br /&gt;
      $this-&amp;gt;release_lock();&lt;br /&gt;
      //  serve the &amp;quot;you are blocked&amp;quot; page &lt;br /&gt;
      header('HTTP/1.0 403 Forbidden');&lt;br /&gt;
      print $this-&amp;gt;block_message;&lt;br /&gt;
      return true;&lt;br /&gt;
    } else {&lt;br /&gt;
     // mark this user looking at a page at this time &lt;br /&gt;
     $now = time();&lt;br /&gt;
     if (! $this-&amp;gt;exclude[$_SERVER['PHP_SELF']]) {&lt;br /&gt;
       $this-&amp;gt;data['user_traffic'][$user]++;&lt;br /&gt;
     }&lt;br /&gt;
     // (sometimes) tote up the list and add bad people &lt;br /&gt;
     if (! $this-&amp;gt;data['traffic_start']) {&lt;br /&gt;
       $this-&amp;gt;data['traffic_start'] = $now;&lt;br /&gt;
     } else {&lt;br /&gt;
       if (($now - $this-&amp;gt;data['traffic_start']) &amp;gt; $this-&amp;gt;recalc_seconds) {&lt;br /&gt;
         while (list($k,$v) = each($this-&amp;gt;data['user_traffic'])) {&lt;br /&gt;
           if ($v &amp;gt; $this-&amp;gt;pageview_threshold) {&lt;br /&gt;
             $this-&amp;gt;data['abusive_users'][$k] = $v;&lt;br /&gt;
             // log the user's addition to the abusive user list &lt;br /&gt;
             error_log(&amp;quot;Abuse: [$k] (from &amp;quot;.$_SERVER['REMOTE_ADDR'].')');&lt;br /&gt;
           }&lt;br /&gt;
         }&lt;br /&gt;
         $this-&amp;gt;data['traffic_start'] = $now;&lt;br /&gt;
         $this-&amp;gt;data['user_traffic'] = array();&lt;br /&gt;
       }&lt;br /&gt;
     }&lt;br /&gt;
     $this-&amp;gt;release_lock();&lt;br /&gt;
    }&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To use this class, call its &amp;lt;tt&amp;gt;check_abuse( )&amp;lt;/tt&amp;gt; method at the top of a page, passing it the username of a logged in user:&lt;br /&gt;
&lt;br /&gt;
 // get_logged_in_user_name() is a function that finds out if a user is logged in &lt;br /&gt;
 if ($user = get_logged_in_user_name( )) {&lt;br /&gt;
     $abuse = new pc_Web_Abuse_Check( );&lt;br /&gt;
     if ($abuse-&amp;gt;check_abuse($user)) {&lt;br /&gt;
         exit;&lt;br /&gt;
     }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;tt&amp;gt;check_abuse( )&amp;lt;/tt&amp;gt; method secures exclusive access to the shared memory segment in which information about users and traffic is stored with the &amp;lt;tt&amp;gt;get_lock( )&amp;lt;/tt&amp;gt; method. If the current user is already on the list of abusive users, it releases its lock on the shared memory, prints out an error page to the user, and returns &amp;lt;tt&amp;gt;true&amp;lt;/tt&amp;gt;. The error page is defined in the class's constructor.&lt;br /&gt;
&lt;br /&gt;
If the user isn't on the abusive user list, and the current page (stored in &amp;lt;tt&amp;gt;$_SERVER['PHP_SELF']&amp;lt;/tt&amp;gt;) isn't on a list of pages to exclude from abuse checking, the count of pages that the user has looked at is incremented. The list of pages to exclude is also defined in the constructor. By calling &amp;lt;tt&amp;gt;check_abuse( )&amp;lt;/tt&amp;gt; at the top of every page and putting pages that don't count as potentially abusive in the &amp;lt;tt&amp;gt;$exclude&amp;lt;/tt&amp;gt; array, you ensure that an abusive user will see the error page even when retrieving a page that doesn't count towards the abuse threshold. This makes your site behave more consistently.&lt;br /&gt;
&lt;br /&gt;
The next section of &amp;lt;tt&amp;gt;check_abuse( )&amp;lt;/tt&amp;gt; is responsible for adding users to the abusive users list. If more than &amp;lt;tt&amp;gt;$this-&amp;gt;recalc_seconds&amp;lt;/tt&amp;gt; have passed since the last time it added users to the abusive users list, it looks at each user's pageview count and if any are over &amp;lt;tt&amp;gt;$this-&amp;gt;pageview_threshold&amp;lt;/tt&amp;gt;, they are added to the abusive users list, and a message is put in the error log. The code that sets &amp;lt;tt&amp;gt;$this-&amp;gt;data['traffic_start']&amp;lt;/tt&amp;gt; if it's not already set is executed only the very first time &amp;lt;tt&amp;gt;check_abuse( )&amp;lt;/tt&amp;gt; is called. After adding any new abusive users, &amp;lt;tt&amp;gt;check_abuse( )&amp;lt;/tt&amp;gt; resets the count of users and pageviews and starts a new interval until the next time the abusive users list is updated. After releasing its lock on the shared memory segment, it returns &amp;lt;tt&amp;gt;false&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
All the information &amp;lt;tt&amp;gt;check_abuse( )&amp;lt;/tt&amp;gt; needs for its calculations, such as the abusive user list, recent pageview counts for users, and the last time abusive users were calculated, is stored inside a single associative array, &amp;lt;tt&amp;gt;$data&amp;lt;/tt&amp;gt;. This makes reading the values from and writing the values to shared memory easier than if the information was stored in separate variables, because only one call to &amp;lt;tt&amp;gt;shm_get_var( )&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;shm_put_var( )&amp;lt;/tt&amp;gt; are necessary.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;tt&amp;gt;pc_Web_Abuse_Check&amp;lt;/tt&amp;gt; class blocks abusive users, but it doesn't provide any reporting capabilities or a way to add or remove specific users from the list. [[PHP Cookbook/Web Basics#phpckbk-CHP-8-EX-8|Example 8-8]] shows the ''abuse-manage.php'' program, which lets you manage the abusive user data.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;phpckbk-CHP-8-EX-8&amp;quot;&amp;gt;&lt;br /&gt;
'''Example 8-8. abuse-manage.php'''&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;// the pc_Web_Abuse_Check class is defined in abuse-check.php&lt;br /&gt;
require 'abuse-check.php';&lt;br /&gt;
&lt;br /&gt;
$abuse = new pc_Web_Abuse_Check();&lt;br /&gt;
$now = time();&lt;br /&gt;
&lt;br /&gt;
// process commands, if any &lt;br /&gt;
$abuse-&amp;gt;get_lock();&lt;br /&gt;
switch ($_REQUEST['cmd']) {&lt;br /&gt;
    case 'clear':&lt;br /&gt;
      $abuse-&amp;gt;data['traffic_start'] = 0;&lt;br /&gt;
      $abuse-&amp;gt;data['abusive_users'] = array();&lt;br /&gt;
      $abuse-&amp;gt;data['user_traffic'] = array();&lt;br /&gt;
      break;&lt;br /&gt;
    case 'add':&lt;br /&gt;
      $abuse-&amp;gt;data['abusive_users'][$_REQUEST['user']] = 'web @ '.strftime('%c',$now);&lt;br /&gt;
      break;&lt;br /&gt;
    case 'remove':&lt;br /&gt;
      $abuse-&amp;gt;data['abusive_users'][$_REQUEST['user']] = 0;&lt;br /&gt;
      break;&lt;br /&gt;
}&lt;br /&gt;
$abuse-&amp;gt;release_lock();&lt;br /&gt;
&lt;br /&gt;
// now the relevant info is in $abuse-&amp;gt;data &lt;br /&gt;
&lt;br /&gt;
print 'It is now &amp;lt;b&amp;gt;'.strftime('%c',$now).'&amp;lt;/b&amp;gt;&amp;lt;br&amp;gt;';&lt;br /&gt;
print 'Current interval started at &amp;lt;b&amp;gt;'.strftime('%c',$abuse-&amp;gt;data['traffic_start']);&lt;br /&gt;
print '&amp;lt;/b&amp;gt; ('.($now - $abuse-&amp;gt;data['traffic_start']).' seconds ago).&amp;lt;p&amp;gt;';&lt;br /&gt;
&lt;br /&gt;
print 'Traffic in the current interval:&amp;lt;br&amp;gt;';&lt;br /&gt;
if (count($abuse-&amp;gt;data['user_traffic'])) {&lt;br /&gt;
  print '&amp;lt;table border=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;User&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Pages&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;';&lt;br /&gt;
  while (list($user,$pages) = each($abuse-&amp;gt;data['user_traffic'])) { &lt;br /&gt;
    print &amp;quot;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;$user&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;$pages&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&amp;quot;;&lt;br /&gt;
  }&lt;br /&gt;
  print &amp;quot;&amp;lt;/table&amp;gt;&amp;quot;;&lt;br /&gt;
} else {&lt;br /&gt;
  print &amp;quot;&amp;lt;i&amp;gt;No traffic.&amp;lt;/i&amp;gt;&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
print '&amp;lt;p&amp;gt;Abusive Users:';&lt;br /&gt;
&lt;br /&gt;
if ($abuse-&amp;gt;data['abusive_users']) {&lt;br /&gt;
  print '&amp;lt;table border=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;User&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Pages&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;';&lt;br /&gt;
  while (list($user,$pages) = each($abuse-&amp;gt;data['abusive_users'])) {&lt;br /&gt;
    if (0 === $pages) {&lt;br /&gt;
      $pages = 'Removed';&lt;br /&gt;
      $remove_command = '';&lt;br /&gt;
    } else {&lt;br /&gt;
      $remove_command = &lt;br /&gt;
         &amp;quot;&amp;lt;a href=\&amp;quot;$_SERVER[PHP_SELF]?cmd=remove&amp;amp;user=&amp;quot;.urlencode($user).&amp;quot;\&amp;quot;&amp;gt;remove&amp;lt;/a&amp;gt;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
    print &amp;quot;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;$user&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;$pages&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;$remove_command&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&amp;quot;;&lt;br /&gt;
  }&lt;br /&gt;
  print '&amp;lt;/table&amp;gt;';&lt;br /&gt;
} else {&lt;br /&gt;
  print &amp;quot;&amp;lt;i&amp;gt;No abusive users.&amp;lt;/i&amp;gt;&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
print&amp;lt;&amp;lt;&amp;lt;END&lt;br /&gt;
&amp;lt;form method=&amp;quot;post&amp;quot; action=&amp;quot;$_SERVER[PHP_SELF]&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;input type=&amp;quot;hidden&amp;quot; name=&amp;quot;cmd&amp;quot; value=&amp;quot;add&amp;quot;&amp;gt;&lt;br /&gt;
Add this user to the abusive users list:&lt;br /&gt;
&amp;lt;input type=&amp;quot;text&amp;quot; name=&amp;quot;user&amp;quot; value=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;Add User&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/form&amp;gt;&lt;br /&gt;
&amp;lt;hr&amp;gt;&lt;br /&gt;
&amp;lt;form method=&amp;quot;post&amp;quot; action=&amp;quot;$_SERVER[PHP_SELF]&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;input type=&amp;quot;hidden&amp;quot; name=&amp;quot;cmd&amp;quot; value=&amp;quot;clear&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;Clear the abusive users list&amp;quot;&amp;gt;&lt;br /&gt;
END;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[PHP Cookbook/Web Basics#phpckbk-CHP-8-EX-8|Example 8-8]] prints out information about current user page view counts and the current abusive user list, as shown in [[PHP Cookbook/Web Basics#phpckbk-CHP-8-FIG-1|Figure 8-1]]. It also lets you add or remove specific users from the list and clear the whole list.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;phpckbk-CHP-8-FIG-1&amp;quot;&amp;gt;&lt;br /&gt;
'''Figure 8-1. Abusive users'''&lt;br /&gt;
&lt;br /&gt;
[[Image:PHP Cookbook_I_8_tt499.png|Abusive users]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When it removes users from the abusive users list, instead of:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;unset($abuse-&amp;gt;data['abusive_users'][$_REQUEST['user']]) &amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
it sets the following to 0:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;$abuse-&amp;gt;data['abusive_users'][$_REQUEST['user']] &amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This still causes &amp;lt;tt&amp;gt;check_abuse( )&amp;lt;/tt&amp;gt; to return &amp;lt;tt&amp;gt;false&amp;lt;/tt&amp;gt;, but it allows the page to explicitly note that the user was on the abusive users list but was removed. This is helpful to know in case a user that was removed starts causing trouble again.&lt;br /&gt;
&lt;br /&gt;
When a user is added to the abusive users list, instead of recording a pageview count, the script records the time the user was added. This is helpful in tracking down who or why the user was manually added to the list.&lt;br /&gt;
&lt;br /&gt;
If you deploy &amp;lt;tt&amp;gt;pc_Web_Abuse_Check&amp;lt;/tt&amp;gt; and this maintenance page on your server, make sure that the maintenance page is protected by a password or otherwise inaccessible to the general public. Obviously, this code isn't very helpful if abusive users can remove themselves from the list of abusive users.&lt;br /&gt;
== Notes ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Docbook2Wiki</name></author>	</entry>

	</feed>