$q expdata .= "protein.expdata NOT LIKE '%nmr%' AND "; $q expdata .= "protein.expdata NOT LIKE '%theor%') OR "; $q expdata .= "protein.expdata = NULL)"; } else { $q expdata = mkLikeOpt("protein.expdata", $expdata); } } else { $q expdata = ""; } This is where things can get hairy. To create a correctly formed query statement, we need to check the existence of the previous clauses generated from each variable. To make the code more compact, I chose to use the ternary conditional operator: condition ? option1 : option2, which we saw in Chapter 6. /* construct and submit the search */ $query = "SELECT DISTINCT site.source id, site.site id, site.metal, site.num ligands, protein.description"; $query .= " FROM protein,site WHERE "; /* check the existence of each part of the query * before sending */ $qtemp = ($q metal!="") ? $q metal : ""; $qtemp .= ($qtemp!="" && $q num lig!="") ? " AND " . $q num lig : $q num lig; $qtemp .= ($qtemp!="" && $q expdata!="") ? " AND " . $q expdata : $q expdata; $qtemp .= ($qtemp!="" && $q r val!="") ? " AND " . $q r val : $q r val; $qtemp .= ($qtemp!="" && $q res!="") ? " AND " . $q res : $q res; $qtemp .= ($qtemp!="" && $q author!="") ? " AND " . $q author : $q author; $query .= $qtemp ; $query .= " AND protein.source id=site.source id ORDER BY site.site id "; $query = strtolower(stripslashes($query)); If we are debugging the script (i.e., if $debug is set to true), let's save some data that can later be readily parsed: /* save to a file for debugging */ if ($debug) { $datestamp = date("Y-m-d#H:m:s#", time()); $dbgfp = fopen ($querylog, "a"); $ip = getenv("REMOTE ADDR"); $agent = getenv("HTTP USER AGENT"); fwrite($dbgfp, "$datestamp"); fwrite($dbgfp, "$ip#$agent#"); fwrite($dbgfp, "$query"); } The code for performing the query and processing the results is similar to that for the pdbsearch.php script above: /* Get the results and process */ $php errormsg=""; @$result = SQL query($query,$link); $msqlerrormsg = SQL error(); if ($php errormsg!="") { echo ("<HTML><BODY BGCOLOR=\"white\"><B>"); echo("PHP Error: "); echo($php errormsg . "<P>"); if ($msqlerrormsg != "") { echo("mSQL Error: " . $msqlerrormsg ); } echo ("</B></BODY></HTML>"); fwrite ($dbgfp, "#ERROR IN QUERY: PHP=" . $php errormsg . " mSQL=" . $msqlerrormsg . "\n"); fclose ($dbgfp); exit; } @$nrows = SQL_num_rows($result); Next we generate a listing of the variables defined by the user. This is usually a good way of providing feedback and also making sure that the input was correctly parsed: /* Output the query variables */ $hitstr = ( $nrows > 1 ) ? " hits" : " hit"; $outvar = "<DIV ALIGN=\"CENTER\"><B>Your query was:</B><BR>"; $outvar .= "[Metal(s)=" . $metal . "] AND [Number of Ligands= " . $num lig; $outvar .= "] AND [Resolution<=" . $res . "] AND [R-value<=" . $r val; $outvar .= "] AND [Expdata=" . $expdata . "] AND [Author(s)=" . $author; $outvar .= "] AND [you asked to show (at the most) " . $showhits; $outvar .= " hits per page. <B>This search found: " . $nrows . $hitstr . "</B></DIV>\n"; echo ($outvar); Finally, we create the table containing the query results, taking into account the maximum number of hits per page that the user specified in the search form. We use the makeForm() function to display the next set of results (if any), using a form that hands the subsequent processing to the prev_next.php script (discussed a bit later in this chapter): /* create hits table only if we got some results */ if ($nrows > 0) { if ($showhits=="all") { $max = $nrows; } else { $max = min((int)$showhits,(int)$nrows); } $rightlower = $max + 1; $rightupper = min(2*$max,$nrows); $showing = "<P><DIV ALIGN=\"CENTER\"><B><U> Showing results: 1 "; $showing .= " to " . (int) $max . "</U></B></DIV>"; echo ($showing); if ($rightlower <= $nrows) { echo ("<DIV ALIGN=\"CENTER\">\n <TABLE WIDTH=\"90%\">\n<TR>\n"); echo ("<TD ALIGN=\"RIGHT\">\n"); makeForm(urlencode($outvar), urlencode($query), $nrows, $max, $rightlower, $rightupper); echo ("</TD>\n"); echo ("</TR>\n</TABLE>\n</DIV>"); } makeTable($result,$max); if ($rightlower <= $nrows) { echo ("<DIV ALIGN=\"CENTER\">\n <TABLE WIDTH=\"90%\">\n<TR>\n"); echo ("<TD ALIGN=\"RIGHT\">\n"); makeForm(urlencode($outvar), urlencode($query), $nrows, $max, $rightlower, $rightupper); echo ("</TD>\n"); echo("</TR>\n</TABLE>\n</DIV>"); } } else { echo ("<DIV ALIGN=\"CENTER\" STYLE=\"background: yellow;\"> <BIG>No sites were found that matched your query </BIG></DIV>"); } If debugging is turned on, we write the number of hits found and close the log file. Finally, we clear the result handle and send the closing HTML tags: if ($debug) { fwrite($dbgfp,sprintf("#%d\n",$nrows)); fclose($dbgfp); } if ($result) SQL free result($result); ?> </TD> </TR> <TR> <TD ALIGN="CENTER" BGCOLOR="#E0FFFF"> <HR WIDTH="50%"> <?php virtual ("/include/nav mdb text.inc") ?> <HR WIDTH="50%"> </TD> </TR> <TR> <TD COLSPAN=2 BGCOLOR="#06F6FA" ALIGN="CENTER"> <?php virtual ("/include/navfoot.inc") ?> </TD> </TR> </TABLE><HR> <ADDRESS> <SMALL> Page maintained by Jesus M. Castagnetto (jesusmc@scripps.edu) - © The Scripps Research Institute. <BR> Query performed on: <? echo (date("D, F d, Y - h:i a",time())) ?> </SMALL> </ADDRESS> </BODY> </HTML> The general script for displaying the previous/next set of hits matching the query parameters is named prev_next.php and is shown below. This is a simple script and does not need to regenerate SQL statements from variables; instead, the SQL statement is URL-encoded and passed into the script in a hidden HTML element. First we send the HTML header/layout to give the page the same look as all the other pages in the MDB site: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML> <HEAD> <META HTTP-EQUIV="Pragma" CONTENT="no cache"> <META NAME="Organization" CONTENT="The Scripps Research Institute"> <META NAME="Copyright" CONTENT="The Scripps Research Institute"> <META NAME="Version" CONTENT="1"> <META NAME="Author" CONTENT="Jesus M. Castagnetto, jesusmc@scripps.edu"> <META NAME="Keywords" CONTENT="metal, protein, metalloprotein, protein design, structure, database, molecular biology, metalloenzyme, metalloantibody, database, query, java"> <META NAME="Description" CONTENT="A structural database of metal-binding sites in metalloproteins, including an interactive search and visualization interface."> <TITLE> MDB (Query Results) - Metalloprotein Site Database and Browser </TITLE> <SCRIPT TYPE="text/javascript" LANGUAGE="JavaScript" SRC="/javascript/mainbar.js"> </SCRIPT> </HEAD> <BODY BGCOLOR="#FFFFFF"> <TABLE BORDER=0 CELLSPACING=0 WIDTH="100%"> <TR> <TD COLSPAN=2> <?php virtual (" /include/navhead.inc") ?> </TD> </TR> <TR VALIGN="top"> <TD BGCOLOR="#06F6FA" ROWSPAN=2> <?php virtual (" /include/navside.inc") ?> </TD> <TD><HR> <?php virtual (" /include/logo.inc") ?> <! END of MDB Logo > <HR><H2 ALIGN="CENTER">Results of querying the MDB</H2> Next, we process the passed-in hidden variables, and require the appropriate files for our SQL function wrappers and for formatting and displaying the results: <?php /* Script: prev next.php * simple script to implement the "Previous results", * "Next results" buttons * * Written by: Jesus M. Castagnetto * Created on: Feb 01, 1999 */ /* included scripts are in the /php inc/ dir */ require ("sql.inc"); require ("tables.inc"); /* (hidden) variables passed to the script: * form str = a string w/ the form entry * sql str = a string w/ the SQL query * max = maximum number of hits per page * lower,upper = bounds for the results range */ $db = "metallodb"; $query = urldecode($sql str); $form = urldecode ($form str); $link = SQL pconnect(); $result = SQL($db,$query . " LIMIT " . $upper ,$link); echo (urldecode($form str)); if ($result) { /* we got some hits */ $leftlower = max(1, $lower - $max); $leftupper = $lower - 1; $rightlower = $upper + 1; $rightupper = min((int)$nrows,(int)($upper + $max)); $showing = "<P><DIV ALIGN=\"CENTER\"><B><U> Showing results: " . (int) $lower; $showing .= " to " . (int) $upper . "</U></B></DIV>"; echo ($showing); echo ("<DIV ALIGN=\"CENTER\">\n <TABLE WIDTH=\"90%\">\n<TR>\n"); if ($leftupper > 0) { echo ("<TD ALIGN=\"LEFT\">\n"); makeForm(urlencode($form), urlencode($query), $nrows, $max, $leftlower, $leftupper); echo ("</TD>\n"); } if ($rightlower <= $nrows) { echo ("<TD ALIGN=\"RIGHT\">\n"); makeForm(urlencode($form), urlencode($query), $nrows, $max, $rightlower, $rightupper); echo ("</TD>\n"); } echo("</TR>\n</TABLE>\n</DIV>"); makeTable($result, $max, $lower, $upper); echo ("<DIV ALIGN=\"CENTER\">\n <TABLE WIDTH=\"90%\">\n<TR>\n"); if ($leftupper > 0) { echo ("<TD ALIGN=\"LEFT\">\n"); makeForm(urlencode($form), urlencode($query), $nrows, $max, $leftlower, $leftupper); echo ("</TD>\n"); } if ($rightlower <= $nrows) { echo ("<TD ALIGN=\"RIGHT\">\n"); makeForm(urlencode($form), urlencode($query), $nrows, $max, $rightlower, $rightupper); echo ("</TD>\n"); } echo("</TR>\n</TABLE>\n</DIV>"); } else { echo ("<BR>No sites with those characteristics were found, try again<BR>\n"); } if ($result) SQL free result($result); ?> And finally the closing HTML tags: </TD> </TR> <TR> <TD ALIGN="CENTER" BGCOLOR="#E0FFFF"> <HR WIDTH="50%"> <?php virtual ("/include/nav mdb text.inc") ?> <HR WIDTH="50%"> </TD> </TR> <TR> <TD COLSPAN=2 BGCOLOR="#06F6FA" ALIGN="CENTER"> <?php virtual ("/include/navfoot.inc") ?> </TD> </TR> </TABLE><HR> <ADDRESS> <SMALL> Page maintained by Jesus M. Castagnetto (jesusmc@scripps.edu) - © The Scripps Research Institute. <BR> Query performed on: <?php echo (date("D, F d, Y - h:i a",time())) ?> </SMALL> </ADDRESS> </BODY> </HTML> If, for example, we performed a search for metal sites containing zinc or copper ions (zn, cu), with 4, 5, or 6 ligands, and with a resolution less than or equal to 2.0 Angstroms, from experimental data obtained by X-ray crystallography, the output will display as below: Here you can see the effect of using makeTable() for displaying the hits, and makeForm() for generating the results navigation button. Summary In this chapter, we looked at a real example of using PHP to connect to a database via the Web. We analyzed the characteristics of a web application, and shown examples on how to implement search interfaces and processing scripts. But I've just scratched the surfaced of what you can do in terms of variable processing. You could, for example, write completely generic SQL generation functions by using a particular format for the variable names, for example naming all the variables with their type appended. Let us supposed that we have implemented an interface for entering data; the name of the variables passed to the script can be of the form: var1:num, var3:str, var4:date. We can now write a simple piece of code to generate the INSERT statement. Firstly, we would need to get the variables from the global array list $HTTP_POST_VARS, and process only those variables that have a type. These will be added to two parallel arrays, one containing the variable name and the other containing the appropriately quoted value: /* get all the variables */ $i=0; while(list($k,$v) = each($HTTP GET VARS)) { if (!strpos($k, ":")) { continue; } else { list($var[$i],$type[$i]) = explode(":",$k); if ($type[$i] == "str" || $type[$i] == "date") { $value[$i] = strtolower("\'" . addslashes($v) . "\'"); } else { $value[$i] = $v; } $i++; } } We can use these two arrays with the implode function in PHP, to generate a correct INSERT statement in a few lines: $query = "insert into " . $table . " (" . implode($var, ",") . ") values ("; $query .= stripslashes(implode($value, ",")) . ")"; When designing a web application with a database backend, the main steps that our scripts have to accomplish will be: ❑ Processing the form variables ❑ Generating the SQL query ❑ Displaying the results Generally the middle step is the one that can be problematic, and we have shown here some functions that allow us to deal with the creation of valid SQL statements. Bear in mind that these are not the only (or maybe the best) solutions, but the ones that were best suited to the MDB site requirements. I am sure that you will come up with much better scripts, and hope that the examples and information here can serve as a starting point in your experimentation with database-backed web applications. A PHP Functions Apache Functions Function Returns Description apache_lookup_ uri(filename) Class Returns as a class information about the URI specified in filename apache_note(no te_name, [note_value]) String Retrieves or (if the second parameter is included) sets values from the notes tables getallheaders( ) Array Returns an array of the HTTP request headers virtual(filena me) Intege r Performs an Apache sub-request such as including a CGI script Array Functions Function Returns Description array( ) Array Creates and returns an array from the supplied parameters array_key s(array) Array Returns an array containing all the keys of the supplied array. Added in PHP 4.0. array_mer ge(arrays ) Array Merges and returns the supplied arrays. Added in PHP 4.0. array_pop (array) Mixed Pops and returns the last element from the end of the array. Added in PHP 4.0. array_pus h(array, variables ) Integer Pushes the supplied variables onto the end of the array; returns the number of elements in the array. Added in PHP 4.0. array_shi ft(array) Mixed Removes and returns the first element from the beginning of the array. Added in PHP 4.0. array_sli ce(array, Array Returns a sub-array from the specified array starting at the offset from the beginning (if positive) or end (if negative) offset, [length]) of the array. If length is positive, it specifies the number of elements the returned array will contain; if negative, it specifies the offset from the end of the original array where the returned array will end. If length is omitted, all elements from offset to the end of the array will be returned. Added in PHP 4.0. array_spl ice(input , offset, [length], [replacem ent]) Array Removes a sub-array from the input array and replaces it with the elements of the replacement array. The offset and length arguments are the same as for array_slice(). Returns an array containing the removed elements. Added in PHP 4.0. array_uns hift(arra y, variables ) Integer Adds the supplied variables to the beginning of the array. Added in PHP 4.0. array_val ues(array ) Array Returns an array containing all the values of the supplied array. Added in PHP 4.0. array_wal k(array, function, [paramete r]) Integer Iterates through the supplied array, applying the named function to every element, passing the element's value as the first parameter and the key as the second; if a third parameter is required, it may be supplied by the parameter argument. arsort(ar ray) Void Sorts the supplied array in descending order, retaining the correlation between keys and values asort(arr ay) Void Sorts the supplied array in ascending order, retaining the correlation between keys and values compact(v arnames) Array Merges the variables and/or arrays named in the varnames argument into a single array. Added in PHP 4.0. count(arr ay) Integer Returns the number of elements in the supplied array current(a rray) Mixed Returns the current element in the supplied array each(arra y) Array Returns a four-element sub-array containing the key and value of the current element from the specified array. The key is contained in elements 0 and "key", the value in elements 1 and "value". end(array ) Void Sets the last element of the supplied array as the current element extract(a rray, [extract_ type], [prefix]) Void Import variables into the symbol table from the supplied array. The extract_type parameter specifies the action to take in case of a collision, and prefix specifies a string to be prefixed to the variable names. in_array( value, array) Boolea n Returns true if the specified value exists in the supplied array. Added in PHP 4.0. key(array ) Mixed Returns the key for the current element in the array ksort(arr ay) Integer Sorts the array by the keys of its elements, retaining the correlation between keys and values list(vari ables) Void Assigns the supplied variables as if they were an array next(arra y) Mixed Moves the array pointer one element forward and returns the next element, or false if the end of the array is reached pos(array ) Mixed Returns the current element from the supplied array [...]... hw_DocByAnchor(co nnection, anchor_id) Integer Returns the object ID of the document to which the anchor belongs hw_DocByAnchorObj (connection, anchor_id) String Returns the object record of the document to which the anchor belongs hw_DocumentAttrib utes(hw_document) String Returns the object record of the document hw_DocumentBodyTa g(hw_document) String Returns the body tag of the document hw_DocumentConten... String Returns the body tag of the document hw_DocumentConten t(hw_document) String Returns the content of the document hw_DocumentSetCon tent(hw_document, content) String Sets the content of the document hw_DocumentSize(h w_document) Integer Returns the size of the document hw_EditText(conne ction, hw_document) Integer Uploads the text document hw_Error(connecti on) Integer Returns the last error number... specified FDF document fdf_create() Integer Creates a new FDF document fdf_get_file(fd f_document) String Returns the value of the /F key in the specified FDF document fdf_get_status( fdf_document) String Returns the value of the /STATUS key in the specified FDF document fdf_get_value(f dfdoc, fieldname) String Returns the value of the named field in the specified FDF document fdf_next_field_ name(fdfdoc, fieldname)... Integer Opens the specified FDF document fdf_save(filena me) Integer Saves the FDF document to the specified file fdf_set_ap(fdf_ document, Void Sets the appearance of the named field in the specified FDF document field_name, face, filename, page_number); fdf_set_file(fd f_document, filename) Void Sets the value of the /F key in the specified FDF document fdf_set_status( fdf_document, status) Void Sets... rendered in the PDF document cpdf_set_text_rise(pd fdoc, value) Void Sets the text rise to the specified value cpdf_set_title(title) Void Sets the title field for the PDF document cpdf_set_word_spacing (pdfdoc, space) Void Sets the word spacing for the specified PDF document cpdf_setdash(pdfdoc, white, black) Void Sets the white and black units for the dash pattern cpdf_setflat(pdfdoc, value) Void Sets... cpdf_rotate(pdfdoc, angle) Void Sets the rotation to the specified angle (in degrees) cpdf_save(pdfdoc) Void Saves the current environment cpdf_save_to_file(pdf doc, filename) Void Saves the specified PDF document to a file cpdf_scale(pdfdoc, xscale, y-scale) Void Sets the scaling for the x- and y-axes cpdf_set_char_spacing (pdfdoc, space) Void Sets the character spacing for the specified PDF document cpdf_set_creator(crea... cpdf_set_creator(crea tor) Void Sets the creator field for the PDF document cpdf_set_current_page (pdfdoc, page_number) Void Sets the page with the specified page_number in the PDF document as the current page cpdf_set_font(pdfdoc, fontname, fontsize, encoding) Void Sets the current font, font size and encoding for the specified PDF document cpdf_set_horiz_scalin g(pdfdoc, scale) Void Sets the horizontal scaling of the... points cpdf_end_text(pdfdoc) Void Ends a text section in the specified PDF document cpdf_fill(pdfdoc) Void Fills the interior of the current path with the current fill color cpdf_fill_stroke(pdfd oc) Void Fills the interior of the current path with the current fill color and draws a line along the path cpdf_finalize(pdfdoc) Void Finalizes the entire PDF document cpdf_finalize_page(pd fdoc, page_number) Void... section in the specified PDF document cpdf_circle(pdfdoc, x, y, radius, [mode]) Void Draws a circle with its center at x, y and with the specified radius The units are the page default if mode is 0 or unsupplied; otherwise postscript points cpdf_clip(pdfdoc) Void Clips drawing to the current path cpdf_close(pdfdoc) Void Closes the specified PDF document cpdf_closepath(pdfdoc ) Void Closes the current... record into an array hw_OutputDocument (hw_document) Integer Prints the document hw_pConnect(host, port, username, password) Integer Opens a persistent Hyperwave connection hw_PipeDocument(c onnection, object_id) Integer Retrieves the document with the specified object_id hw_Root() Integer Returns the root object ID hw_Unlock(connect ion, object_id) Integer Unlocks the document with the specified object_id . specified FDF document fdf_create() Integer Creates a new FDF document fdf_get_file(fd f_document) String Returns the value of the /F key in the specified FDF document fdf_get_status( fdf_document). postscript points. cpdf_clip(pdfdoc) Void Clips drawing to the current path cpdf_close(pdfdoc) Void Closes the specified PDF document cpdf_closepath(pdfdoc ) Void Closes the current path. path cpdf_finalize(pdfdoc) Void Finalizes the entire PDF document cpdf_finalize_page(pd fdoc, page_number) Void Finalizes the specified page in the specifies PDF document cpdf_import_jpeg(pdfd oc,