ONLamp.com    
 Published on ONLamp.com (http://www.onlamp.com/)
 See this if you're having trouble printing code examples


PHP and Heredocs

by Daniel Smith
04/10/2003

PHP is wonderful for generating dynamic web pages. The ability to combine the PHP, HTML, and SQL statements within a script provides a lot of power. Combining everything in one file, however, can make a big mess.

Having separate files for PHP classes, HTML blocks, and SQL statements can go a long way towards cleaner, more understandable web application design and implementation. This article explains the use of heredocs and associative arrays in "resource files".

A Big Problem: Ugly Code

Many of us have encountered PHP code like this:

<?php
print "<html><head>";
print "<title>sample</title>";
print "</head>";
print "<body><p>List of photo table:</p>";
print "<table>\n";

$connection = mysql_connect("some_host", "some_user",
                            "some_password");
$the_db = mysql_select_db("our_db", $connection);
$result = mysql_query("select * from photo_table");
while ($current_line = 	mysql_fetch_assoc($result)) {
    print "<tr>\n";
    foreach ($current_line as $cur_field) {
        print "<td>$cur_field</td>\n";
    }
    print "</tr>\n";
}
print "</table>";

...etc...

?>

Related Reading

Web Database Applications with PHP, and MySQL
By Hugh E. Williams, David Lane

This is a simple example, lacking JavaScript or fancy HTML/CSS, but you can see how quickly things can get out of hand. Jumping between languages can be fun for a quick and dirty script, but it's a nightmare to navigate for anything substantial. You could go back and forth between separate sections of HTML and PHP within a file, to eliminate using print to produce HTML, but even this approach can be messy.

Another problem is dividing the work. Look again at the example and imagine a team consisting of a database person, a great client-side HTML/UI designer, and you, the PHP coder in the middle of it all, trying to glue everything together. How can the three of you make much progress with everything stuck together in the same file?

When you extract hardwired HTML and SQL from your PHP application, you give yourself a lot of flexibility. Start treating these separate parts of an application as "resources" that can be loaded from separate files. Among other benefits, separating these concerns allows you to easily switch between different HTML UIs, each available in multiple languages. Your database person can optimize statements without affecting the client-side designer, and you can spend more time dealing with straight PHP.

What technique should we use in our separate resource files to make large blocks of SQL and HTML available to our PHP classes?

Heredocs!

Consider large chunks of HTML or SQL statements larger than a few lines. How would you assign these to a variable? One approach is to escape every quote:

$some_var = "select * from photos where comment =\"some comment\";"; 

That's a simple example. Once you start mixing single and double quotes, for example, in names such as "O'Reilly", the potential for missing backslashes increases.

Heredocs have no quote problems. They allow you to assign a variable with one or more lines of text in a much more straightforward fashion. I think of a heredoc as a "document, right here". Here is a simple example of assigning a SQL statement to illustrate the syntax:

$sql_list_collections =<<<EOD
SELECT collection_id,collection_dir,title,description, cover_photo_id
FROM collection
ORDER BY collection_dir ASC;
EOD;

EOD can be thought of as "End Of Data". You can choose any terminating string you wish, as long as it

My next example is much more complex and demonstrates the use of associative arrays and quoting. I scrub input variables (checking them for validity) from a form, and toss them into an associative array called $VALS. Associative arrays make very convenient containers for related variables that have been checked and need to be passed to different functions.

$sql_insert_photo =<<<EndSQL
INSERT INTO collection (title, major_category, subcategory,
                       location, event_date, collection_dir)
        VALUES ('{$VALS['title']}',  '{$VALS['major_cat']}',
               '{$VALS['subcat']}', '{$VALS['location']}',
               '{$VALS['date']}',   '{$VALS['unique_path']}');
EndSQL;

Heredocs allow you to concentrate on the contents of the variable, worrying less about print statements and backslashes. For large blocks of HTML, this is crucial. A client-side person can focus on what they're doing, instead of getting involved with the PHP side of things.

An Important Note On Security...

When passing any variables in to write a SQL statement, be sure to check them for validity. You want to avoid the possibility of SQL injection attacks and address any quoting issues.

Resources And Templates: Using Heredocs

Let's shift our focus to using include files. When a file is included, we can pick up variables from it. Rather than dealing with myriad variables, each resource I pick up from my include file becomes an element in an array called $RSRC. Bear in mind that once-defined, you will treat this array as read-only.

Let's look at a function within a PHP class that reads in a resource file:

/*
**    get_global_resources - update $RSRC[]
**
**    This is intended for resource files that
**    do not change from one language or theme to another.
**    One example is SQL statements
*/
function get_global_resources(&$RSRC, $context,
                              $VALS = array())
{
    // $VALS is an associative array, it allows us to
    // expand vars of the form: $VAL['foo'] within the
    // resource file..
    include "/usr/local/php-project/include/global/$context";
}

It's a pretty short function. I can optionally pass in an associative array, $VALS, for use in expanding variables within resources. This is what a call to get_global_resources() looks like:

$this->get_global_resources($RSRC, "d-col.res", $VALS);

This is the sample resource file (d-col.res) itself:

<?php
$RSRC['SQL_HEAD_QUERY'] =<<<EOD
SELECT  collection.title AS title,
        collection.description AS description,
        collection.comments AS comments,
        collection.collection_dir AS collection_dir
FROM  collection
WHERE 
      collection.collection_id = {$VALS['collection']};
EOD;

$RSRC['SQL_BODY_QUERY'] =<<<EOD
SELECT photo.photo_id AS photo_id,
        photo.keywords AS keywords,
        photo.caption AS caption,
        photo.file_type AS file_type
FROM  collection LEFT JOIN photo
        ON photo.collection_id = collection.collection_id
WHERE 
      collection.collection_id = {$VALS['collection']}
GROUP BY photo.photo_id;
EOD;
?>

Another Important Security Note:

While I show the use of variables in an include path, it is crucial to note that values (such as $context in the get_global_resources() function) come from predefined constants and configuration values. If user values are to affect an include path in your web app, then user values should serve as an index into an associative array with known good paths. Never have user-values map directly to an include path without first checking their validity.

Some quick notes:

What Do The Included Resources Look Like?

Now that we're pulling in things from resource files, it's helpful to check on the $RSRC variable. I use the Apache web server and like to scan the logs with tail -f logs/error_log to see what's happening. Rather than cluttering up the browser side with a lot of diagnostic output, I use:

function p_dbg($an_array, $array_info = "(no info given)")
{
    static $dbg_count = 0;

    ob_start();
    $dbg_count++;
    print "[" . $dbg_count . "] $array_info\n";
    print_r($an_array);
    $the_str = ob_get_contents();
    ob_end_clean();
    
    error_log("in p_dbg: ");
    error_log($the_str);
}

Then I check on my resources via a debugging call:

$this->p_dbg($RSRC, "photo collection RSRC");

The HTML Side Of Things

At this point, we can assign chunks of text to PHP associative array variables by including external files which contain heredocs. We can expand variables within the assignments and can easily access the contents at runtime for debugging. Now that we have separated database calls from PHP classes, let's turn our attention to HTML.

There are several ways to separate the presentation of HTML from the logic of PHP, including Smarty, the well known template package. (Smarty has an extensive set of capabilities, and is worth exploring.) For this project, using heredocs and substituting database values for placeholders is sufficient and straightforward. Here is a simple HTML template file:

<?php
$template =<<<EOD_PD
<html>
<head>
<title>Photo Display: FP_PHOTO_ID...</title>
</head>
<body>
FP_TOOLBAR
<p>
<center>
<img src="FP_FULL" />
<br />
FP_CAPTION
</center>
</p>
</body>
</html>
EOD_PD
?>

In my project, this particular file is at (project_path)/include/ui-default/en/photo-display.php. I access the file within my PHP classes like this:

$display_photo_page = $this->get_template("photo-display.php");

where get_template() looks like:

function get_template($which_file)
{
    global $FP_LANG;
    global $FP_UI;

    include
    "/usr/local/php-files/include/$FP_UI/$FP_LANG/$which_file";
    
    return $template;
}

Once again, it's straightforward. I have a couple of variables which control the UI to display. My HTML UI could be simple or complex, depending on the end user and their browser. For every UI, I have the option of grabbing a template that matches the language being used on the browser side. You can look at ($_SERVER['HTTP_ACCEPT_LANGUAGE'] to find the language being used and default to something like en (for English) when nothing is set. Keeping security in mind, I do not take the values of $FP_LANG and $FP_UI directly from the user. Instead, I use user side information such as browser variables and cookie preferences as indexes into known good values.

The placeholders are consistent between languages and UIs. For this example, I used:

Filling In The Template

At this point we have seen a method to set a $RSRC array, and our HTML template. The next steps are to

In order to show these in context, here is a stripped down example from my production code. Here is a SQL call, defined in a resource file:

$RSRC['SQL_PHOTO_DISPLAY'] =<<<EOD
SELECT collection.collection_id AS collection_id,
	   collection.collection_dir AS collection_dir,
	   photo.comments AS comments
FROM  photo, collection
WHERE
	 photo_id = '{$VALS['photo_id']}'
AND  collection.collection_id = photo.collection_id;
EOD;

And here is the function that ties everything together:

/*
**    display - show a photo
**
**    $db - an object that contains a handle to a MySQL database
**    $photo_id - user input (i.e. 000045) via a link (GET)
*/
function display($db, $photo_id)
{
	$P = &$this;
	$RSRC = array();
	$VALS = array();
	
	$VALS['photo_id'] = $P->check_id($photo_id);
	$P->get_global_resources($RSRC, "d-photo.res", $VALS);
	
	// toolbar is just a simple HTML menu.  It gets substituted
	// into the main page later
	$toolbar            = $P->get_template("toolbar.php");
	$display_photo_page = $P->get_template("photo-display.php");
    
	// a sample debugging call.  Use tail -f on an Apache error_log
	// to see the output
	$P->p_dbg($RSRC, "photo display RSRC");
	
	// $db is an object with an open handle into our database
	$sql_result = $db->query($RSRC['SQL_PHOTO_DISPLAY']);
	$row        = mysql_fetch_assoc($sql_result);
	
	$collection_dir = $row['collection_dir'];
	
	/*
	** in real code, we ask the database for the filetype, and
	** map to a filename extension from that... here we're
	** just coming up with a relative path, based on
	** the collection directory and photo id.
	*/
	$photo_path = $P->get_web_fullsize($collection_dir) .
						$VALS['photo_id'] . ".jpg";
                      
	// here is how we build up an array of replacements
	// that will be done on the HTML template
	$all_replace['FP_TOOLBAR']       = $toolbar;
	$all_replace['FP_FULL']          = $photo_path;
	$all_replace['FP_CAPTION']       = $row['caption'];
	$all_replace['FP_COLLECTION_ID'] = $row['collection_id'];
	
	// do the replacements (replace() shown a little farther down)
	$P->replace($display_photo_page, $all_replace);
	print $display_photo_page;
}

One last piece of code is the replace function. It transforms HTML templates into database-driven output:

/*
**    replace - do multiple replacements on $str, using $repl hash array
*/
function replace(&$str, $repl)
{
    while (list ($key, $val) = each($repl)) {
        $str = ereg_replace("$key", "$val", $str);
    }
    return $str;
}

Summary

In this article we have gone through the steps of separating program logic, HTML templates, and SQL statements. Our method hinges on a few key concepts:

I have written a small example which illustrates this approach. The example additionally separates the calling PHP file (in an htdocs/ directory) from the PHP classes. I also show a method to switch between different HTML interfaces and different languages.

I hope this method helps you in your PHP scripting projects.

Daniel Smith is currently working on an Open Source web-based photography database in Apache/MySQL/PHP.


Return to the PHP DevCenter.

Copyright © 2009 O'Reilly Media, Inc.