Tải bản đầy đủ (.pdf) (94 trang)

The Essential Guide to Dreamweaver CS4 with CSS, Ajax, and PHP phần 9 ppsx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (2.58 MB, 94 trang )

9. Remove the square brackets after operating_system in $_POST['operating_
system[]'] (line 41 in the preceding screenshot) so that it looks like this:
GetSQLValueString($_POST['operating_system'], "text"),
T
his inserts the value of
$
_POST['operating_system']
i
nto the table as a string,
but the values being sent from the checkbox group are an array. So, they need to
be reformatted before they can be inserted into the database.
10. To convert the array in $_POST['operating system'] into a comma-separated
string, add the following code block highlighted in bold immediately after the code
shown on line 39 in the preceding screenshot:
if ((isset($_POST["MM_insert"])) && ($_POST["MM_insert"] == "form1")) {
// convert the checkbox group subarray to a string
if (isset($_POST['operating_system'])) {
$_POST['operating_system'] = implode(',', $_POST['operating_system']);
} else {
$_POST['operating_system'] = 'none';
}
$insertSQL = sprintf("INSERT INTO os_poll (operating_system) ➥
VALUES (%s)",
The block is enclosed in the server behavior’s conditional statement that executes
the code only if the form has been submitted. Because checkboxes and multiple-
choice lists don’t appear in the $_POST array if nothing has been selected, the new
code first checks whether any values have been selected for operating_system.If
they have, they are converted to a comma-separated string with implode().
Otherwise, none is assigned as the value. This is needed to prevent the SQL query
from throwing an error.
The first argument to the implode() function is the string you want to act as a sep-


arator between array elements. It’s vital to use a comma with no space on either
side like this:
$_POST['operating_system'] = implode(',', $_POST['operating_system']);
If you add a space after the comma inside the first argument, only the first value is
inserted in the SET column. This is because the space is treated as part of the string.
The extra space after the first comma in the following line of code will result in
incomplete data being inserted into the SET column:
$_POST['operating_system'] = implode(', ', $_POST['operating_system']);
11. Save set_insert.php, and load it in a browser. Test the form by selecting at least
two checkboxes and clicking
Submit.
12. Check the results by clicking the Browse tab in
phpMyAdmin. Confirm that you can see the selected
values inserted in the table, as shown here:
Check your code, if necessary, against set_insert_01.php in examples/ch17.
HANDLING CHECKBOX GROUPS, SEARCHES, AND DATES
749
17
Retrieving data stored in a SET column
The MySQL documentation ( clas-
sifies the SET data type as a string, so that’s what it expects you to insert, and that’s what
it returns when you retrieve data with a SELECT query. However, as I explained earlier, the
values are stored numerically, rather than as text. This has the following important effects
on the data that you get back from a SET column:
Values are returned as a comma-separated list.
Trailing spaces are automatically deleted.
Even if the INSERT query contains duplicate values, each value is stored only once.
This means you can’t adapt the form in set_insert.php to record how many com-
puters of a different type a person owns.
Values are returned in the same order as the original table specification. The results

from a search of the os_poll table will always be in the order Windows, Mac, Linux.
You can’t use a SET column to store items in order of preference. It’s purely a yes
or no choice.
The values stored for each record in a SET column can be accessed in the normal way
through a recordset, as the following exercise shows.
This exercise shows different ways of displaying values retrieved from a SET column.
Continue working with set_insert.php and the os_poll table from the previous exercise.
1. With set_insert.php open in the Document window, open the Recordset dialog box
in Simple mode, and use the following settings to create a recordset called
getVote:
Even though this uses only a SELECT query, I’m using the administrative user
account because it makes more sense to use the same connection as the INSERT
query on the same page.
Displaying the user’s vote
THE ESSENTIAL GUIDE TO DREAMWEAVER CS4 WITH CSS, AJAX, AND PHP
750
Setting Sort to vote_id Descending uses the primary key to sort the recordset in
reverse order, so the most recent record will always be the first.
2. Underneath the insert form, add a paragraph with the text You selected:, and use
the
Bindings panel to insert a dynamic text placeholder for operating_system from
the getVote recordset. The bottom of the page should look like this:
3. Save set_insert.php, and click the Live View button.
Click
Yes when asked about updating the copy on the
testing server. The bottom of the page should look
similar to this:
Hang on a moment . . . You can’t submit the form in Live
view, yet the dynamic text is displaying a result. It is, of
course, the result from the previous vote. You want to display it only after the visitor

has voted. So, the recordset code needs to go inside the conditional statement that
controls the INSERT query, but it must come after the vote has been registered.
4. Open Code view, and locate the code shown in Figure 17-4. Study the code care-
fully. In Chapter 15, I told you that Dreamweaver always puts the code for record-
sets immediately above the
DOCTYPE declaration. On this occasion, though, the
getVote recordset code is on lines 34–38, more than 20 lines above the DOCTYPE
declaration, with the Insert Record server behavior in between.
HANDLING CHECKBOX GROUPS, SEARCHES, AND DATES
751
17
Figure 17-4.
Because the Insert Record
server behavior has been
edited, the recordset has
been inserted above it.
This has happened because you edited the Insert Record behavior in the previous
exercise, so Dreamweaver no longer recognizes it. However, it recognizes the
GetSQLValueString() function declaration as part of its own code, so it puts the
recordset with it. Because you’re moving the recordset code anyway, it doesn’t
really matter where Dreamweaver put it. However, this illustrates the importance
of understanding what each block of code does and where it’s located. PHP code is
processed in the same order as it appears in the script. If you left the code in its
current location and surrounded it with a conditional statement to run only after
the form has been submitted, it would work, but it would always show the previous
result rather than the current one because it’s executed before the INSERT query.
5. Cut the getVote recordset code (lines 34–38 in Figure 17-4) to your clipboard, and
paste it in front of the closing curly brace shown on line 57 of Figure 17-4. This
ensures that the recordset is created only when the form is submitted and that it
gets the most recent result.

6. Also, you want to show the result only when the recordset has been created. You
can do this by surrounding the paragraph that displays it with a conditional state-
ment that checks whether the recordset has been created like this:
<?php if (isset($getVote)) { ?>
<p>You selected: <?php echo $row_getVote['operating_system']; ?></p>
<?php } ?>
The $getVote variable contains the recordset result, so it must exist if the record-
set does. (See why it’s a good idea to give recordsets meaningful names?)
7. Since the recordset is created only when the form is submitted, you also need a
conditional statement around the code that clears the recordset result at the end
of the script like this:
</html>
<?php
if (isset($getVote)) {
mysql_free_result($getVote);
}
?>
8. Save set_insert.php, and load it into a browser. When the page first loads, the
form looks the same as before, but when you submit the form, it displays your
choice immediately below as a comma-separated string, as shown in Figure 17-5.
THE ESSENTIAL GUIDE TO DREAMWEAVER CS4 WITH CSS, AJAX, AND PHP
752
Figure 17-5.
The values stored in a
SET column are
returned as a comma-
separated string.
If you select just one value, there is no comma, but when more than one value is
returned, they are separated by commas with no space in between.
9. How you handle the comma-separated string depends on what you want to do with

the results of a recordset that contains a SET column. If you simply want to add a
space after each comma, you can use the str_replace() function like this:
<?php echo str_replace(',', ', ', $row_getVote['operating_system']);?>
The str_replace() function takes three arguments: the string you want to replace,
what you want to replace it with, and the string you want to perform the replace-
ment on. So, the first argument here is a comma on its own, the second argument
is a comma followed by a space, and the final argument is the value from the
recordset.
10. To format the comma-separated string in more complex ways, pass the value from
the recordset to the explode() function, and store it in a variable like this:
$selected = explode(',', $row_getVote['operating_system']);
The explode() function converts a string into an array. It normally takes two argu-
ments: a string containing the character(s) that mark(s) the boundary between
each array element, and the string you want to split. The boundary characters are
discarded, so this converts a comma-separated string into an array, which you can
then manipulate however you like. For example, you could display the results as a
bulleted list like this:
<p>You selected: <?php $selected = explode(',', ➥
$row_getVote['operating_system']); ?></p>
<ul>
<?php foreach ($selected as $item) {
echo "<li>$item</li>";
}
?>
</ul>
You can check your code against set_insert_02.php in examples/ch17.
The purpose of this exercise is to demonstrate the use of SET columns, not to build a real-
istic online poll. To prevent multiple submissions by the same person, an online poll also
needs a column that records a value that can be used to identify someone who has already
voted. One way of doing this is to create a session variable that contains a randomly gen-

erated value like this:
session_start();
if (!isset($_SESSION['random_id'])) {
$_SESSION['random_id'] = md5(uniqid(rand(), true));
}
This uses the PHP function uniqid() ( />uniqid.php) in combination with md5(), an encryption function, and rand(), which gener-
ates a random value, to create a 32-character string. Store the session variable in a hidden
HANDLING CHECKBOX GROUPS, SEARCHES, AND DATES
753
17
form field, and check that it doesn’t already exist in the database before inserting it with
the poll data.
I’ll come back later to showing you how to find records that contain specific values in a
SET column.
Getting the information you want from a
database
As you have probably realized by now, a recordset is the result of a database search.
Controlling the search is a SQL query using the SELECT command. Dreamweaver builds the
PHP code that passes the SQL query to the database and processes the result. It can also
build the SQL query for very simple searches. For anything more sophisticated, it’s up to
you to build the query yourself. Over the next few pages, I’ll show you how to tackle some
common search problems. However, writing SELECT queries is a massive subject, about
which whole books have been written (one of my favorite writers on MySQL is Paul
DuBois). So, treat the following pages as an introduction to a fascinating and rewarding
subject, rather than a definitive guide to search queries.
Books and online forums can provide a lot of help in formulating the appropriate SELECT
query to extract the information that you want from a database. But to use that informa-
tion successfully with Dreamweaver, you need to understand how the Recordset dialog
box builds a SELECT statement.
Understanding how Dreamweaver builds a SQL query

The file find_author_01.php in examples/ch17 contains a form with a single text field
called first_name and a submit button. Beneath the form is a table with a single row in a
repeat region, which displays the results of the search. Load the page into a browser, type
William in the text field, and click Search. You should see a list of authors whose first name
is William, as shown here:
THE ESSENTIAL GUIDE TO DREAMWEAVER CS4 WITH CSS, AJAX, AND PHP
754
Try some other names, such as John, Dorothy, and Mae, and a list of matching records is
displayed. By default, text searches in MySQL are case-insensitive, so it doesn’t matter
what combination of uppercase and lowercase you use. We’ll get to case-sensitive and par-
tial-word searches later, but let’s look at the code that Dreamweaver uses to submit the
query to the database.
I created the getAuthors recordset in find_author_01.php using the following settings in
the
Recordset dialog box in Simple mode:
The same query looks like this in Advanced mode:
The first thing to note is that Dreamweaver doesn’t add the table name in front of each
column name when you use the
Recordset dialog box in Simple mode. As explained in the
previous chapter, adding the table name is necessary only when the same column name is
used in more than one table (like author_id in the authors and quotations tables).
Simple mode is capable of handling only single tables, so there’s never any danger of
ambiguity. However, Dreamweaver automatically adds the table names to all columns
HANDLING CHECKBOX GROUPS, SEARCHES, AND DATES
755
17
when you build a query in Advanced mode. It does so as a precautionary measure, even if
there’s no likelihood of ambiguity.
The other thing to note is that the
Filter settings from Simple mode have been converted

to this:
WHERE first_name = colname
Dreamweaver uses
colname to represent the unknown value that will be passed to the SQL
query through the text field in find_author_01.php. The properties of colname are
defined in the
Variables area below, with Type set to Text, Default value to -1, and Run-time
Value
to $_GET[‘first_name’].
It’s important to realize that colname is not part of SQL. Dreamweaver uses the concept of
replacement when dealing with unknown values in SQL queries. When you close the
Recordset dialog box, Dreamweaver replaces colname with PHP code that inserts the run-
time value into the query. The choice of colname is purely arbitrary. It can be anything that
doesn’t clash with the rest of the query. In the previous chapter, you used var1 and var2
as the names for runtime variables.
The other important thing to know about Dreamweaver’s use of runtime variables is that
the PHP code automatically encloses the value in quotes unless you specify
Type as Integer
or Floating point number. Because strings must be enclosed in quotes, the correct way to
write this query in SQL is as follows (assuming that you’re searching for “William”):
SELECT first_name, family_name
FROM authors
WHERE first_name = 'William'
ORDER BY family_name ASC
Because Dreamweaver handles the quotes automatically, you need to adapt SQL from
other sources accordingly.
Now, look at the PHP code generated by these settings (see Figure 17-6).
Figure 17-6. The code Dreamweaver generates for a recordset that uses a variable passed through a
query string
You have seen the recordset code on many occasions, and I described the meaning of the

variables in Chapter 15. What I would like you to focus on here is the way Dreamweaver
THE ESSENTIAL GUIDE TO DREAMWEAVER CS4 WITH CSS, AJAX, AND PHP
756
handles colname and uses it to insert the runtime variable into the SQL query. The follow-
ing sequence of events takes place:
1. The name of the variable defined in the Recordset dialog box (in this case,
colname) is combined with the recordset name on line 34 to create a PHP variable
($colname_getAuthors), which is assigned a default value of -1.
2. The conditional statement on lines 35–37 replaces the default value with the sub-
mitted value from the form. In this case, it uses $_GET['first_name']. So if a
variable called first_name is passed through a query string at the end of the
URL, $colname_getAuthors takes its value. Otherwise, $colname_getAuthors
remains -1.
3. The code shown on line 39 of Figure 17-6 builds the SQL query using a PHP func-
tion called sprintf().
The sprintf() function can be difficult to get your head around, but it takes a minimum
of two arguments. The first of these is a string that contains one or more predefined place-
holders; the number of remaining arguments matches the number of placeholders in the
first argument. When the script runs, sprintf() replaces each placeholder with its corre-
sponding argument.
Why use such a convoluted way of inserting something into the SQL query? It’s a short-
hand way of passing the runtime variables to another function without the need to assign
the result to a variable. Dreamweaver passes all runtime variables to a custom-built func-
tion called GetSQLValueString(), which is shown in Figure 15-1. As explained in Chapter
15, this function protects your database from malicious attacks known as SQL injection.If
Dreamweaver didn’t use sprintf(), it would need to store the result of passing each run-
time variable to GetSQLValueString() before building the query. It also avoids complex
problems with escaping quotes with a lot of variables.
The most commonly used predefined placeholder used with sprintf() is %s, which stands
for “string.” So, the colname that you saw in the

Recordset dialog box becomes %s, and
when the script runs, it is replaced by the result of GetSQLValueString($colname_
getAuthors, "text").
When there’s more than one runtime variable in a SQL query, Dreamweaver replaces each
one with %s and passes it to GetSQLValueString() when listing the variable as an argu-
ment to sprintf().
Dreamweaver uses sprintf() to build all SQL queries, not just for recordsets. The impor-
tant things to remember about editing SQL queries in Dreamweaver or adapting queries
that you read about elsewhere are as follows:
The number of arguments following the first one passed to sprintf() must be the
same as the number of %s placeholders in the query.
GetSQLValueString() automatically handles quotes around text values, so you
should never add quotes around the %s placeholder in sprintf().
HANDLING CHECKBOX GROUPS, SEARCHES, AND DATES
757
17
Troubleshooting SQL queries
At the end of line 40 in Figure 17-6 is this rather doom-laden command:
or die(mysql_error());
This tells the script to stop running if there’s a problem with the SQL query and to display
the error message returned by MySQL. Figure 17-7 shows what happens if you add single
quotes around the %s placeholder in the SQL query in find_author_01.php.
Figure 17-7. MySQL error messages look cryptic but are very useful.
The error is reported as being on line 1, because the message comes from MySQL, not
PHP. MySQL sees only the query, so the error is always on line 1. The important informa-
tion is the reference to the error being “near” a particular part of the query. The error is
always immediately preceding the segment quoted in the message, but the only way to
diagnose the problem is to study the contents of the query.
Don’t waste time trying to analyze the code. As I explained in Chapter 15, the SQL query is
stored in a variable called $query_recordsetName. Dive into Code view, and use echo to

display the query onscreen, as shown in the following illustration (use line breaks to sepa-
rate the query from the error message):
THE ESSENTIAL GUIDE TO DREAMWEAVER CS4 WITH CSS, AJAX, AND PHP
758
You can then load the page into a browser and see exactly what is being sent to the data-
base. In the case of
find_author_01.php, the query is displayed as soon as you load the
page (see Figure 17-8). In some cases, you need to pass the necessary values to the query
through the form or as part of a query string in the browser address bar. You might see a
lot of error messages onscreen, but that’s not important. As long as you can see what the
SQL query contains, you can get to the root of the problem.
Figure 17-8. Displaying the contents of a SQL query onscreen is the best way to analyze MySQL
errors.
At first glance, the output in Figure 17-8 seems OK, but on closer inspection, what looks
like a pair of double quotes around
-1 is, in fact, four single quotes (if you try this yourself,
use the browser’s
View Source option to see the output in monospaced type). This is what
MySQL actually sees:
SELECT first_name, family_name
FROM authors
WHERE first_name =''
-1'' ORDER BY family_name ASC
The extra pair of quotes around -1 results in the value of first_name being an empty
string. This is followed by
-1 and another pair of quotes, none of which makes any sense,
so MySQL gives up.
Even if you can’t spot the problem yourself, you can copy the output and paste it into a
question in an online forum. You’re much more likely to get a helpful response by show-
ing what’s being passed to the database and giving details of the MySQL error message.

You can use this technique with all SQL queries, not just
SELECT ones.
Let’s take a look at various search operations, beginning with the choice of method for
search forms.
Choosing GET or POST for search forms
All the forms you have built so far in this book have used the POST method. This has been
the appropriate choice for several reasons. First, the
POST method is more secure than GET,
because the values are not sent through a query string at the end of the URL. Moreover,
the maximum length of 2,083 characters in a URL imposed by Internet Explorer makes the
GET method impractical for many database insert forms.
So, if the POST method is more secure, why not use it for everything? The answer is con-
venience. Passing the search criteria as variables through a query string at the end of the
URL makes it easy to save search results as bookmarks in a browser. So, it’s usual to use the
GET method when creating search forms.
HANDLING CHECKBOX GROUPS, SEARCHES, AND DATES
759
17
When choosing between POST and GET, use the following as a general guide:
If sending an email or modifying a database, use POST.
If searching a database, use GET.
You might think there’s a contradiction inherent in this advice. After all, when updating or
deleting a record from a database, its primary key is sent through a query string and
retrieved from the $_GET array. However, the primary key sent through the query string is
used only to search for details of the record. The actual updating or deletion is done by a
form that uses the POST method. That’s why it’s important to build a confirmation page for
deleting records. Using GET for any operation that directly modifies database records is an
invitation to disaster. That’s why I also recommend setting up a user account that has only
SELECT privileges to prevent an attacker from modifying your data.
Using numerical comparisons

As you’ve already seen, a single equal sign in a SQL query looks for an exact match. You
can also use comparison operators, such as > (greater than) and < (less than). This would
be of more practical value in a price list, where you’re looking for something cheaper or
more expensive than a particular amount, but you can see it in action using the primary
key column of the authors table, which uses numbers.
In find_author_02.php, I changed the text input field in the form to author_id. Then
I changed the
Filter setting in the Recordset
dialog box in Simple mode like this:
This changes the WHERE clause to this:
WHERE author_id < colname
The
Type of colname is changed to Integer, and its Runtime Value is changed to
$_GET['author_id']. Because the default is left at -1, nothing is displayed when the page first
loads, but if you enter a number and click the
Search button, you see a list of all authors
with a primary key less than the figure entered.
This is a rather trivial example, but if you go through the various Filter options in Simple
mode and examine the SQL in Advanced mode, you’ll quickly learn how the operators are
used in a SQL query. Dreamweaver uses <> as the “not equal to” operator instead of !=.
Either is perfectly acceptable.
At the bottom of the
Filter drop-down menu are three options: begins with, ends with, and
contains. These perform wildcard searches, where the user enters only part of the search
term. In previous versions of Dreamweaver, this type of filter failed when you used any of
these options with a numeric column. However, this problem has been fixed in
Dreamweaver CS4. If you switch to Advanced mode, you’ll see that the
Type of colname is
For a greater-than comparison, the default needs to be higher than any existing value
in the column. If you leave it at

-1, all records are displayed when the page first loads.
THE ESSENTIAL GUIDE TO DREAMWEAVER CS4 WITH CSS, AJAX, AND PHP
760
changed to Text. Although this might appear to be a bug, it is, in fact, the correct way to
perform a wildcard search. The SQL generated by Dreamweaver uses LIKE, which must be
followed by a string, not a number. I’ll come back to wildcards when discussing text
searches later in the chapter.
Although the
Filter options in Simple mode have their uses, they’re not very practical in a
real-world situation. Normally, you want a search form to offer the user a variety of options.
That’s where an understanding of the code generated by Dreamweaver becomes invaluable.
Roll up your sleeves to create something a little more practical.
This exercise enhances
find_author_02.php by adding a drop-down menu that gives the
user the option to choose how the comparison should be performed—greater than, less
than, equal to, or not equal to. The selection is passed to the SQL query as a form variable.
Since Dreamweaver has options only for numbers and text, you need to do some elemen-
tary hand-coding.
1. Copy find_author_02.php from examples/ch17, and save it as find_author_
03.php
in workfiles/ch17.
2. Click inside the Author_id label to the left of the text field, select the <label> tag in
the Tag selector at the bottom of the Document window, and press the right arrow
key once to position the insertion point correctly between the label and text field.
3. Select List/Menu from the Forms tab of the Insert bar (or use the Form submenu of
the
Insert menu). In the Input Tag Accessibility Attributes dialog box, enter operator in
the
ID field, leave Label blank, select No label tag, and click OK.
4. Click the List Values button in the Property inspector, and enter the following oper-

ators in both the
Item Label and Value fields: =, !=, <, <=, >, and >=. Although you
don’t normally need to set the
Value field if it’s the same as Item Label, you need to
do it on this occasion, because Dreamweaver replaces the less-than and greater-
than operators with HTML entities.
5. Select the equal sign as Initially Selected.
6. Open Split view, and edit the value properties of the <option> tags to change the
HTML entities to the less-than and greater-than operators. Leave the HTML entities
intact between the opening and closing <option> tags. The page should look like this:
Performing user-controlled comparisons
HANDLING CHECKBOX GROUPS, SEARCHES, AND DATES
761
17
7. In Code view, scroll up to locate the following section of code:
8. You need to replace the < in the WHERE clause (shown on line 39 of the preceding
screenshot) with a variable and define it in the same way as Dreamweaver has done
with colname. Begin by positioning your cursor on the blank line shown on line 33
and inserting the following code:
// define the operator variable and give it a default value
$operator = '=';
// define an array of acceptable operators
$permittedOperators = array('=', '!=', '<', '<=', '>', '>=');
// get operator value from form, if submitted
if (isset($_GET['operator']) && in_array($_GET['operator'], ➥
$permittedOperators)) {
$operator = $_GET['operator'];
}
This sets $operator to a default value of an equal sign, defines an array of accept-
able operators, and reassigns the value submitted from the form, if it exists and is

one of the permitted operators. Using the $permittedOperators array and
in_array() like this performs a similar security check to the $expected array that
you used with the feedback form in Chapter 11. Any variable that’s passed to a SQL
query should be scrutinized to prevent SQL injection.
9. Now edit the SQL query (shown on line 39 of the preceding screenshot) like this
(new code is highlighted in bold):
$query_getAuthors = sprintf(“SELECT first_name, family_name ➥
FROM authors WHERE author_id %s %s ORDER BY family_name ASC", ➥
$operator, GetSQLValueString($colname_getAuthors, "int"));
As explained earlier in “Understanding how Dreamweaver builds a SQL query,”
sprintf() uses %s as a placeholder and replaces each one in order by the sub-
sequent arguments passed to the function. So, the form values are both passed to
the SQL query in a secure manner; the first %s is replaced by the operator, and the
second one is replaced by the value entered in the text field.
10. Save the page, and test it in a browser. Enter 32 in the text field, and click Search.
William Shakespeare should be displayed. Change the operator to
!=, and perform
the same search. All authors except Shakespeare are displayed, and so on.
You can check your code against find_author_03.php in examples/ch17.
THE ESSENTIAL GUIDE TO DREAMWEAVER CS4 WITH CSS, AJAX, AND PHP
762
The value of the drop-down menu in the preceding exercise always resets to the equal
sign. If you want the previous selection to be redisplayed, you need to add conditional
statements to each <option> tag. The following code shows the first two tags:
<option value="=" <?php if (isset($_GET['operator']) && ➥
$_GET['operator'] == '=' || !isset($_GET['operator'])) {
echo 'selected="selected"';
}?>>=</option>
<option value="!=" <?php if (isset($_GET['operator']) && ➥
$_GET['operator'] == '!=') {

echo 'selected="selected"';
}?>>!=</option>
Searching within a numerical range
There are two ways to specify a range in SQL. One is to use >= (greater than or equal to)
for the bottom end of the range and <= (less than or equal to) for the top end. The alter-
native is BETWEEN . . . AND. Both require two input fields. This means setting two vari-
ables, so you’re obliged to use the
Recordset dialog box in Advanced mode. The files
find_author_04.php and find_author_05.php in examples/ch17 have been modified by
adding a second text input field and naming the two fields min and max. The recordset set-
tings in find_author_04.php look like this:
I have used var1 and var2 as the runtime variables and given them both the same settings,
as shown in the preceding screenshot
(Run-time Value for var1 is $_GET['min']).
The only difference in
find_author_05.php is the WHERE clause in the SQL query, which
looks like this:
WHERE authors.author_id BETWEEN var1 AND var2
If you test both pages in a browser, they produce identical results. As long as you enter a
number in both fields, you should see a list of authors’ names (unless, of course, the min-
imum is greater than the highest number in the table).
HANDLING CHECKBOX GROUPS, SEARCHES, AND DATES
763
17
Now try entering a value in just the minimum field. As you might expect, there are no
results. This is hardly surprising, because the default value of var2 (which controls the
maximum) is set to -1. So, try just the maximum field. Again, no results. This is more puz-
zling, because the default for the minimum field is also -1, so you would expect to get a
list of authors whose primary keys belong in the range from 1 (since primary keys can’t be
negative) to whatever you entered in the maximum field.

You need to look at the code to understand what’s happening.
This exercise helps explain how the default value of a runtime variable is used in a SQL
query. It also shows how to tweak the Dreamweaver code to influence the way default val-
ues are used. You can use either
find_author_04.php or find_author_05.php, because
the PHP code is identical.
1. In the Server Behaviors panel, double-click Recordset (getAuthors) to open the
Recordset dialog box. Select var2 in the Variables field, click the Edit button, and
change
Default value to 10. Since var2 is the runtime variable for max, this resets the
default maximum.
2. Save the changes, and load the page into a browser. The names of the first ten
authors are displayed after the form.
3. Enter a number between 1 and 9 in the Minimum field, but leave the Maximum field
empty. Click
Search. It doesn’t matter what number you choose; nothing is dis-
played. So, what’s happened to the default you set in step 1?
4. To find out, open Code view, and locate the code that sets the default values. It
looks like this:
The code shown on line 38 sets the default value of $var2_getAuthors to 10.
However, the conditional statement on lines 39–41 resets it if the value of
$_GET['max'] has been defined. I imagine that many of you will be scratching your
head at this point. Surely, if the field is left blank, the value isn’t defined? Wrong. It
is defined—as an empty string. As a result, if you enter
5 in the Minimum field, the
WHERE clause in find_author_04.php is converted to this:
WHERE authors.author_id >= 5 AND authors.author_id <=
Similarly, the WHERE clause in find_author_05.php has no maximum. Without it,
the SQL query returns no results. It doesn’t trigger any error messages either,
Experimenting with the default value

THE ESSENTIAL GUIDE TO DREAMWEAVER CS4 WITH CSS, AJAX, AND PHP
764
because a valid value is passed to the query. The problem is that it’s a number you
want, not an empty string.
5. To preserve the default number when a blank field is submitted, change the code
shown on line 39 like this:
if (isset($_GET['max']) && !empty($_GET['max'])) {
6. Test the page again. This time, if you leave the Maximum field blank, the script uses
10 as its default value. Of course, you can override this by entering a different num-
ber in the field. But if you leave the
Minimum field blank, you still get no results. It
needs to be changed in the same way if you always want a default value to be used
when the form is submitted.
Is this a bug in Dreamweaver? It depends on your point of view. When creating runtime
variables in
Simple mode, Dreamweaver always uses -1 as its default value. This ensures
that a search form displays no results when the page first loads. This is usually what you
want, but you should ask, “Why bother to run the SQL query when the page first loads?”
It’s inefficient to submit a query to the database when no search criteria have been
defined.
The more efficient way to prevent the display of recordset results when a search form first
loads is to wrap the recordset code in a conditional statement and execute the SQL query
only when the search form has been submitted. If you name the submit button search,
you can use the following code:
if (array_key_exists('search', $_GET)) {
// recordset code goes here
}
This is the same technique as used in Chapter 11 to make sure that the client-side valida-
tion of the feedback form is run only after the form has been submitted. Since the record-
set isn’t created when the page first loads, you need to wrap the table that displays the

recordset results in a similar conditional statement. You also need to amend this block of
code below the closing </html> tag:
<?php
mysql_free_result($recordsetName);
?>
Change it like this:
<?php
if (isset($recordsetName)) mysql_free_result($recordsetName);
?>
A fully commented version of this code is in find_author_06.php in examples/ch17. Only
the form is displayed when the page first loads. If nothing is entered in either or both of
the text fields when the form is submitted, the default values are used. Otherwise, the
search is based on the values entered into each field. This results in a much more efficient
way of searching through a numerical range.
HANDLING CHECKBOX GROUPS, SEARCHES, AND DATES
765
17
Searching for text
Searching for text follows the same basic principles, but there are more options, because
you frequently need to base text searches on partial information. For example, you might
want to find all authors whose family name begins with “S,” or you might want to search
for quotations that contain the word “winter.” In some cases, you might also want the
search to be case-sensitive.
Making a search case-sensitive
As explained earlier, text searches in MySQL are, by default, case-insensitive. To enforce
case sensitivity, you simply add the keyword BINARY in front of the runtime variable.
In find_author_01.php (see “Understanding how Dreamweaver builds a SQL query” ear-
lier in the chapter), the SQL query looks like this:
SELECT first_name, family_name
FROM authors

WHERE first_name = colname
ORDER BY family_name ASC
When the form is submitted, colname is replaced by the value in the first_name field. To
make the search case-sensitive, change the WHERE clause like this:
WHERE first_name = BINARY colname
The SQL query in find_author_07.php performs a case-sensitive search. Enter
John in the
search field, and you get three results. Enter
john, JOHN, or any other combination of
uppercase and lowercase letters, and you’ll see no results.
Displaying a message when no results are found
It’s not very user-friendly to leave users wondering whether a search is still being per-
formed or whether it simply produced no results. The Show Region server behavior, which
was introduced in Chapter 14, makes it easy to display a special message if nothing is
found, but it’s inappropriate to show the message until a search has been executed.
This brief exercise shows you how to add a message to find_author_07.php to tell a user
that no results were found. The default code generated by Dreamweaver needs to be
edited slightly if you don’t want the message to appear when the page first loads.
1. Copy find_author_07.php from examples/ch17, and save it in workfiles/ch17 as
find_author_08.php.
2. Click inside the search form, select <form#form1> in the Tag selector at the bottom
of the Document window, and press your right arrow key once to place the inser-
tion point outside the closing </form> tag.
Using the Show Region server behavior
THE ESSENTIAL GUIDE TO DREAMWEAVER CS4 WITH CSS, AJAX, AND PHP
766
3. Press Enter/Return to insert a new paragraph, click the Bold button in the HTML
view of the Property inspector, and type
No results found.
4. Click the <p> tag in the Tag selector to highlight the paragraph that you have just

inserted, and select
Show Region ➤ Show If Recordset Is Empty f
rom the
Server
Behaviors
panel menu (the same options are also available on the Data tab of the
Insert bar and the
Data Objects submenu of the Insert menu).
5. The dialog box that opens has only one option: for you to select the recordset.
Since there’s only one on this page, it automatically selects the correct one, so just
click
OK. This surrounds the selected paragraph with a gray border and a Show If
tab at the top-left corner, indicating that it’s controlled by a conditional statement.
6. Save the page, and load it into a browser. As the following screenshot shows, the
No results found message is automatically displayed:
This is because of the way Dreamweaver handles runtime variables (see “Searching
within a numerical range” earlier in the chapter). Unless you wrap the recordset
code in a conditional statement, as described earlier, the SQL query is submitted to
the database when the page first loads. The default value of -1 deliberately pre-
vents any results from being found, so the message is displayed.
There are two ways to get around this. One is to wrap the code in conditional state-
ments as described earlier (the Show Region server behavior code needs to go
inside the conditional statement that controls the display of results). The other,
simpler solution is to edit the Show Region server behavior code. This time, we’ll
take the second option.
7. Select Show If Recordset is Empty (getAuthors) in the Server Behaviors panel to select
the server behavior code, and switch to Code view. The code should be highlighted
like this:
HANDLING CHECKBOX GROUPS, SEARCHES, AND DATES
767

17
8. You want the code in this conditional statement to be executed only if the form
has been submitted, so amend the code shown on line 62 like this:
<?php if (array_key_exists('search', $_GET) && $totalRows_getAuthors➥
=
= 0) { // Show if
f
orm submitted and
r
ecordset empty ?>
Changing the code like this prevents you from editing the Show Record server
behavior in the
Server Behaviors panel, but it tidies up the display of your search
form. When you reload the page into a browser, the message is hidden until you
conduct a search that genuinely produces no results.
Check your code, if necessary, against find_author_08.php in examples/ch17.
Searching multiple columns
Frequently, text searches are based on matching multiple criteria or alternatives. SQL uses
AND and OR to build such queries. The meaning is self-explanatory. To search for an author
by both first name and family name, create a second runtime variable, such as colname2,
and change the WHERE clause to this:
WHERE first_name = colname AND family_name = colname2
To search on the basis of either first name or family name, change the WHERE clause to this:
WHERE first_name = colname OR family_name = colname2
Examples of this are in find_author_09.php and find_author_10.php, respectively, in
examples/ch17. The file find_author_11.php shows an example of passing AND or OR as a
runtime variable to the SQL query using the same technique as described earlier in
“Performing user-controlled comparisons.”
Using wildcard characters in a search
In SQL, the equal sign looks only for an exact match. All the examples so far have used the

authors table, where each column normally contains only a single word. A search for
“William” produces two results: William Shakespeare and William Wordsworth. However, a
search for “Will” produces no results. You might also want to search for all family names
beginning with “S” or search the quotations table for all entries that include “winter.”
When searching through columns that contain short text entries or numbers, you can use
wildcard characters in your search. For longer sections of text, you should consider creat-
ing a FULLTEXT index, which I’ll describe later in this chapter.
MySQL has two wildcard characters: the underscore (_) matches a single character, and the
percentage sign (%) matches any number of characters. A particularly useful feature about
% is that it also matches nothing. This means that a search for “Will%” matches both
William and Will on its own. Consequently, most wildcard searches use %.
To use a wildcard character in a SQL query in Dreamweaver, add it to the beginning, end,
or both ends of the runtime variable. Also, replace the equal sign with the keyword LIKE.
THE ESSENTIAL GUIDE TO DREAMWEAVER CS4 WITH CSS, AJAX, AND PHP
768
So, to search for authors based on the first part of their name, change the WHERE clause in
find_author_09.php like this:
WHERE first_name LIKE colname% AND family_name LIKE colname2%
You can test this in find_author_12.php. Start by entering the first part of a name in both
fields. For example, if you type
W in the First name field and S in the Family name field, the
result is
William Shakespeare. Try it again, just typing W in the First name field. You should
see four results.
Pause a moment to think about this. The SQL query uses AND, so shouldn’t there be
something in both fields? To understand what’s happened, repeat the test with
find_author_13.php. The SQL query is identical, but the page displays the query along
with the results, as shown in Figure 17-9.
Figure 17-9. Using AND with a wildcard character search allows a field to be left blank.
Although nothing is entered in the second field, the wildcard character % is added to the

end of the runtime variable. This results in the second condition matching the
family_name column with %—in other words, anything.
Now try it with find_author_14.php, where the only difference is that AND has been
changed to OR.
If you enter values in both fields, you’ll get the results that you expect. However, if you
leave one of the fields blank, you’ll always get a full list of all records. This is because the
query tells the database to match anything in one of the fields.
HANDLING CHECKBOX GROUPS, SEARCHES, AND DATES
769
17
Adding % at the front of a runtime variable lets you search for words that end with a par-
ticular letter or series of characters. Putting % at both ends of a runtime variable finds the
search expression in the middle of a string; and since % can also match nothing, it means
the search term can be anywhere—at the beginning, in the middle, at the end—or it can
even be the full string itself.
So, let’s bring the quotations table into our search.
This exercise adapts the SQL query used in quote_list.php in the previous chapter.
Instead of displaying a list of all quotations and their authors, it uses a runtime variable
with % at both ends to search for quotations that contain a specific word or phrase. To save
you time, I have created find_quote_01.php in examples/ch17 for you to use as a starting
point. The finished code is in find_quote_02.php.
1. Copy find_quote_01.php to workfiles/ch17, and open it in the Document win-
dow. The page contains a form with a single text input field called searchTerm,a
submit button, and code to display the results of the search.
2. Double-click Recordset (getQuote) in the Server Behaviors panel to open the
Recordset dialog box. The SQL query looks like this:
SELECT authors.first_name, authors.family_name, quotations.quotation
FROM quotations LEFT JOIN authors USING (author_id)
ORDER BY authors.family_name
It’s based on the query in quote_list.php in Chapter 16 (it doesn’t get quote_id

and uses a simpler ORDER BY clause). Click the
Test button, and you’ll see every
quotation listed with its author’s name.
3. To search for quotations containing a particular word or phrase, you need to add
the quotation column to the WHERE clause. In the
Database items section at the bot-
tom of the
Recordset dialog box, expand Tables, and highlight quotation in the
quotations tree menu. Click the WHERE button to add it to the SQL query. The query
should now look like this:
SELECT authors.first_name, authors.family_name, quotations.quotation
FROM quotations LEFT JOIN authors USING (author_id)
WHERE quotations.quotation
ORDER BY authors.family_name
Searching for quotations that contain a word or phrase
This illustrates an important difference between SQL and PHP. When it encounters OR,
the PHP engine doesn’t bother to evaluate the second half of the condition if the first
half is true. In a SQL query, however, both sides are evaluated. So, in the first case, the
SQL query finds authors whose first name begins with “W” AND whose family name is
anything. In the second case, it finds authors whose first name begins with “W” OR
whose family name is anything. Creating searches with wildcards can be confusing, so
it’s a good idea to display the SQL query onscreen while testing to understand why you
get the results you do.
THE ESSENTIAL GUIDE TO DREAMWEAVER CS4 WITH CSS, AJAX, AND PHP
770
4. Add LIKE %var1% to the end of the WHERE clause, click the plus button alongside
Variables, and define the runtime variable var1 using the following settings:
Name: var1
Type
: Text

Default value
: -1
Runtime value
: $_GET['searchTerm']
The settings in the SQL and Variables fields should now look like this:
5. Click OK to close the Recor
dset
dialog box, save the page, and load it into a
browser. The quotations contain a lot of seasonal references, so enter
summer or
winter in the Search for field. You should see a list of quotations that contain the
search term.
6. Searches with the % wildcard aren’t limited to single words. Try entering just x in the
Search for field. You should see a quotation from Winston Churchill that contains
the word “except.”
7. You can also search for a phrase. Enter red, r
ed rose
, and click the Search button.
You should see the following result:
Note that the phrase must be exact and must not be enclosed in quotes.
Check your code, if necessary
, against find_quote_02.php in examples/ch17.
HANDLING CHECKBOX GROUPS, SEARCHES, AND DATES
771
17
This type of wildcard search works fine for even quite large databases. I use it on a data-
base that contains more than 14,000 records, and the search results are normally displayed
in one or two seconds. If you need to do a lot of text searches, you might consider FULL-
TEXT indexing, which offers a more sophisticated range of text search options.
Using a FULLTEXT index

Creating a FULLTEXT index on the column(s) you want to search does away with the need
for wildcard characters. You can use FULLTEXT searches in a number of ways, but the fol-
lowing are the most useful:
Natural language searching: This finds all words passed to the query as a runtime
variable. So, a search for “winter discontent” (without the quotes) in the
quotations table returns all records that contain either “winter” or “discontent.”
Searching in Boolean mode: This lets the user refine the search by preceding
required words with a plus sign (+) and words to be excluded by a minus sign (–).
So, a search for “+winter +discontent” (without the quotes) in the quotations
table would find the Shakespeare quotation about “the winter of our discontent”
but exclude all other records. Boolean mode also permits the use of double quotes
to specify exact phrases and the asterisk (*) as a wildcard character.
These are significant advantages to FULLTEXT, but it does have the following limitations:
Only MyISAM tables support FULLTEXT indexes. You cannot add a FULLTEXT index
to InnoDB tables. So, you need to choose between maintaining referential integrity
with foreign key constraints and FULLTEXT searching.
Only CHAR, VARCHAR, and TEXT columns can be included in a FULLTEXT index.
Words that occur in more than 50 percent of the records are ignored.
Words that contain fewer than four characters are ignored.
More than 500 common words, such as “the,” “also,” and “always,” are designated
as stopwords that are always ignored, even if preceded with a plus sign in Boolean
mode. See />for the full list of stopwords.
Only full words are matched unless the wildcard asterisk is used in a Boolean
search.
Boolean mode does not work in MySQL 3.23.
A FULLTEXT index can be created to search multiple columns simultaneously.
However, all columns must be in the same table.
The syntax for a FULLTEXT search is different from a wildcard search with LIKE. The WHERE
clause for a natural language search looks like this:
WHERE MATCH (columnName) AGAINST ('searchTerm')

For a Boolean search, it looks like this:
WHERE MATCH (columnName) AGAINST ('searchTerm' IN BOOLEAN MODE)
THE ESSENTIAL GUIDE TO DREAMWEAVER CS4 WITH CSS, AJAX, AND PHP
772
You can test FULLTEXT searching with find_quote_03.php and find_quote_04.php in
examples/ch17. The SQL query in find_quote_03.php performs a natural language search
and looks like this:
SELECT authors.first_name, authors.family_name, quotations.quotation
FROM quotations LEFT JOIN authors USING (author_id)
WHERE MATCH (quotations.quotation) AGAINST (var1)
ORDER BY authors.family_name
The query in find_quote_04.php searches in Boolean mode and looks like this:
SELECT authors.first_name, authors.family_name, quotations.quotation
FROM quotations LEFT JOIN authors USING (author_id)
WHERE MATCH (quotations.quotation) AGAINST (var1 IN BOOLEAN MODE)
ORDER BY authors.family_name
Since these are text searches, it goes without saying that the
Type of the runtime variable
must always be set to
Text.
Before you can use the example files, you need to add a FULLTEXT index to the
quotations table. If you used the InnoDB version of the quotations table, you also need
to remove the foreign key constraints and convert it to MyISAM first. Detailed instructions
on how to do this were given in Chapter 16.
Adding a FULLTEXT index to a MyISAM table in phpMyAdmin is as simple as clicking a button.
1. If it’s not already open, launch phpMyAdmin, and display the quotations table
structure in the main frame.
2. Click the Fulltext icon in the quotation row, as shown in Figure 17-10.
Adding a FULLTEXT index
HANDLING CHECKBOX GROUPS, SEARCHES, AND DATES

773
17
Figure 17-10. You can apply a FULLTEXT index easily in phpMyAdmin.
As you can see from Figure 17-10, the Fulltext icon is grayed out for quote_id and
author_id, because they’re not capable of taking a FULLTEXT index. If the icon is also
grayed out for
quotation, it probably means that the table is still using the InnoDB
storage engine. You must convert the table to MyISAM first.
That’s all there is to adding a FULLTEXT index.
A FULLTEXT index is best suited to very large text databases. When building the database,
it’s recommended that you add the index after the data has been imported.

×