<<

. 56
( 132 .)



>>

, primary key (author_addr_id)
, unique key (author_addr)
)
type=InnoDB
;

As you can see, this code makes use of MySQL™s auto_increment feature to get
a unique value per entry. There is potential here. If you want to add some adminis-
trative functions to this application you can add a foreign-key constraint with an ON
DELETE CASCADE qualifier to the main topics table that references author_addrs.
Then just remove the row from author_addrs for the problematic poster and poof,
all his or her posts disappear. Bear in mind, though, that the responses to those
posts would remain, and the threads the poster started would be orphaned unless
they were manually deleted. There are always improvements to be made.



Code Overview
As we mentioned earlier, two main functions are involved in this application:

— Displaying a listing of posts

— Inserting a new post to the database
Chapter 10: Threaded Discussion 321

Thus, it should come as little surprise that at the base level there are only two
files: display_topic.php and write_topic.php. In addition to these files, you have a
separate file that stores all of your functions (functions.php). If you read the previ-
ous section, you probably won™t be surprised to find that most of the effort
involved in developing this application, and therefore most of the code we are
introducing, relates to displaying the ancestors and children of a particular post.
Keep in mind that a post can have any number of ancestors and any number of
children. So your script has to be flexible enough to deal with a post with one reply
or twenty.
The portion that writes a topic to the database should be pretty easy to deal with.
In your form you need to have hidden fields that mark the root_id and the
parent_id, and you want to validate the contents of the forms, but other than that
it should be pretty easy. The next section of the chapter breaks it down.



Code Breakdown
As usual, most of the fun occurs in the functions. In fact, in this application the
only two files actually referenced by URLs are practically empty.

Reusable functions
Again, this application makes use of the functions described in Chapter 8 and
Chapter 9. In addition, in this example we introduce some error-handling functions,
to give you a finer level of control over what gets displayed to the end user when
something goes wrong. When your users can complain about error messages right
there in the site itself, this is a topic worth some attention. The code involved is a
little more complex than what we™ve seen so far, so we™ll look at it at the end of this
chapter, after we™ve gotten a better idea of what™s supposed to happen when things
go right.

Functions from /book/discussion/functions
The application itself has just a few functions, one of which uses a technique that
requires some explanation. The concept that is new to this application is called
recursion. It comes up in the display_kids() function.

display_kids()
Usually, in this part of the book, a function is displayed and then described.
However, this function must be treated a bit differently because recursion can be
somewhat difficult conceptually. So before we display the function, we want to take
a look at recursion. (If you already know your way around recursive functions, feel
free to skim.)
322 Part IV: Not So Simple Applications

The important thing to keep in mind is that you have no idea how deep any
thread will be: There can be one level of replies, or twenty. So to properly lay out
all the children of any one thread you need a very, very flexible script. It needs to
do something like this:

print current topic
while the current topic has unprocessed child topics
set current topic to next unprocessed child
go to line 1
end while
if current topic has a parent
set current topic to that parent and go to line 2
else
exit
end if

This block of code must be repeated indefinitely until no other answers exist. But
how do you tell a script to do something until it runs out of answers? The looping
mechanisms we™ve discussed so far won™t really work. The for..., while..., and
do...while loops that we talked about in Chapter 5 and used in the previous chap-
ters are of no help.
If that isn™t clear, take a look at Table 10-6 and the code that follows.


TABLE 10-6 SAMPLE TABLE

topic_id root_id parent_id subject name date

1 1 0 Nachos rule Jay 3/12/2003
2 2 0 Cheetos are the best Ed 3/12/2003
3 1 1 Re: Nachos rule Don 3/12/2003
4 1 3 Re: Nachos rule Bill 3/13/2003
5 5 0 What about cookies Evany 3/13/2003
6 2 2 Re: Cheetos are the best Ed 3/14/2003
7 1 4 Cheetos, are you kidding Jeff 3/15/2003
8 5 5 Re: What about cookies Jay 3/15/2003




Suppose you want each level to be indented a little farther than the previous
one, by means of the HTML blockquote tag. Now, assume that you™re calling the
following function by passing a topic_id of 7.
Chapter 10: Threaded Discussion 323

function RecurForMe($topic_id)
{
$query = “SELECT * from topics WHERE topic_id = $topic_id;
$result = mysql_query($query) or
die(“Query failed”);
$row = mysql_fetch_array($result);
echo “<blockquote>”;
echo $row[“name”], “\n”;
RecurForMe($row[“parent_id”]);
}

You know by now not to actually run a script like this because there™s no error
checking, and eventually, when no responses to the query exist, it will cause an
error. We wrote this function so you could look at the last line. You see what it
does: The function calls itself. If you™re not clear about the impact this has, walk
through it with us.
The first time through, given the $topic_id of 7, the query returns (surprise,
surprise) row number 7. Then a blockquote tag and the name (Jeff) is printed out.
Then the function calls itself, this time passing the parent_id of 4. The next time
through the query returns row 4, the time after that it returns row 3, and finally, it
returns row 1. When the function is done, the script output (before the error) looks
like this:

<blockquote>Jeff
<blockquote>Bill
<blockquote>Don
<blockquote>Jay

The display_kids() function works in pretty much the same way: It calls itself
for as long as necessary. But in the final script you have to take a lot more into con-
sideration. For example, the description field of immediate children is printed out,
but farther down the ancestral path you show only subject and name. Before you
get caught up in the larger script, look at how to change layout based on ancestry
in your simplified script.

function RecurForMe($topic_id, $level = 1)
{
$query = “SELECT * from topics WHERE topic_id = $topic_id”;
$result = mysql_query($query);
$row = mysql_fetch_array($result);
echo “<blockquote>”;
echo $row[“name”], “\n”;
if($level == 1) {echo $row[“subject”];}
RecurForMe($row[“parent_id”], $level + 1);
}
324 Part IV: Not So Simple Applications

We™ve added another variable ($level) to this function to keep track of the level.
The default value is 1, and it is incremented each time through. The first time
through the subject is printed, but in subsequent iterations it is not.


Recursion can be an expensive process: It takes up quite a bit of processor
time if it goes too far. To prevent a system from being overwhelmed, you
might want to limit the depth of any topic.



Armed with this information, you should be able to get through the
display_kids() function. A lot of info is in there to ensure good layout, but other
than that it™s all pretty readable. Some comments help you get through.


In the code that follows, note the line
$topic_id = (int)$topic_id;
This is an example of casting to an int value. Casting to an int is an easy
way of adding safety to your application ” you™re always sure you™ll get an
int to work with.



function display_kids ($topic_id=0, $level=0)
{
$topic_id = (int)$topic_id;
$level = (int)$level;

// make sure that we aren™t caught in some terrible loop
if ($level > 50)
{
$private_error = “trying to display topic $topic_id, level
is $level - bailing”;
user_error(“Error: Recursion too deep”, E_USER_ERROR);
exit;
}

// retrieve topic records from the MySQL database having
// this topic_id value in their parent_id column (i.e. those
// for whom this topic is the parent_topic

$query =
“select topic_id, name, author
, date_format(create_dt,™%b %e %Y %r™) as create_dt
Chapter 10: Threaded Discussion 325

, description
, author_addr_id
from topics
where parent_id = $topic_id
order by create_dt, topic_id”
;

$result = my_query($query);
$output = ˜™;
while ($r = mysql_fetch_assoc($result))
{
extract($r, EXTR_PREFIX_ALL, ˜r™);

if (empty($r_author))
{
$r_author = ˜[no name]™;
}

if ($r_topic_id != $topic_id)
{
$tag = anchor_tag(
˜index.php?topic_id=™.$r_topic_id
, $r_name
);
}
else
{
// this should never happen, but just in case -
// don™t print a link back to this topic
$tag = $r_name;
}

$tag .= “ by <b>$r_author</b> (ID#$r_author_addr_id) on
<b>$r_create_dt</b>”;

if ($level)
{
// non-zero level - use unordered list format
$output .= li_tag($tag);
}
else
{
// zero (first) level - print inside a table
$output .= table(array(
˜width™ => ˜75%™
326 Part IV: Not So Simple Applications

, ˜rows™=>array(
table_row(table_cell(array(
˜bgcolor™=>™skyblue™
, ˜colspan™=>2
, ˜value™=>$tag
)))
, table_row(
table_cell(array(
˜width™=>™5™
, ˜value™=>™&nbsp;&nbsp;™
))
, $r_description
)
)
));
}

<<

. 56
( 132 .)



>>