hey guys,
this is the first class I've ever written from scratch. this started out as a very simple, very ugly, search code, first using
LIKE %thing%
, but it kept growing, and getting more ugly, and harder to read. so i decided to rewrite it all, but as a class. the script searches a mysql db that holds data about scholarly papers using full-text search, with optional boolean mode. so here it is, my first php search class:
class search {
/* usage:
* $search = new search($string, $boolean, $mode);
*
*/
var $mode; //are we doing a search(1), or displaying all(2)?
var $string; //holds the search string
var $time; //how long it took to do the query
var $result; //holds the sql result resource
var $page_nav; //holds the formatted page navigation string w/ links
var $num_rslts; //holds the total number of results found
var $boolean; //true/false - is boolean mode inabled?, later holds
var $sql; //holds the sql query
var $display_q; //holds the display query
var $limits; //holds the limit, 'extends' the $sql var, like: $sql.$limits
/* Class Constructor */
function search($string, $boolean, $mode){
$this->string = $this->clean($string);
$this->boolean = $boolean;
$this->mode = $mode;
$this->run_search();
}
/* clean - makes the string ready/safe to be put into a query */
function clean($string){
if($string == NULL){
return NULL;
}else{
return mysql_real_escape_string($string);
}
}
/* turns true/false boolean mode into the query operator, will also do the reverse operation */
function make_boolean(){
switch ($this->boolean){
case true:
$this->boolean = " IN BOOLEAN MODE";
break;
case false:
$this->boolean = "";
break;
case " IN BOOLEAN MODE":
$this->boolean = true;
break;
case "":
$this->boolean = false;
break;
}
}
/* make the sql string */
function make_sql(){
switch ($this->mode){
//were doing a search
case 1:
$this->make_boolean();//switch to sql format
$this->sql = "SELECT *, MATCH(".SEARCH_FIELDS.") AGAINST ('".$this->string."'"
.$this->boolean.") AS score FROM ".TBL_PAPERS." WHERE MATCH("
.SEARCH_FIELDS.") AGAINST ('".$this->string."'".$this->boolean.") "
."ORDER BY score DESC".$this->limits;
$this->make_boolean();//switch back to true/false
break;
//were not searching so show all
case 2:
$this->sql = "SELECT * FROM ".TBL_PAPERS.$this->limits;
break;
}
}
//lets run the query
function run_search(){
global $database, $timer;
$this->make_sql();
//lets figure out how long it took to run the query
include("userspace/include/timer.php");
$timer = new BC_Timer;
$timer->start_time(); //start the timer
//run the query
$this->result = $database->query($this->sql) or die(mysql_error());
$this->num_rslts = mysql_num_rows($this->result);
$this->make_pg_nav();//make the page navigation
$this->make_sql();
$this->result = $database->query($this->sql) or die(mysql_error());
//stop the timer, must be done before make_display_q()
$timer->end_time();
$this->time = number_format($timer->elapsed_time(), 4);
$this->make_display_q();
//$this->highlight();
}
//makes pagination and printable navigation links ($this->page_nav)
function make_pg_nav(){
if(isset($_GET['page']) && strlen($_GET['page'])>0){
$current_page = $_GET['page'];
}else{
$current_page = 1;
}
// figure how many total pages
$number_of_pages = floor($this->num_rslts / NUM_ROWS);
if(($this->num_rslts % NUM_ROWS)>=1){
$number_of_pages++;
}
// create page navigation links at bottom of table, as needed
if($number_of_pages <= 1){
$this->page_nav = "";
}else{
// we need to ensure that if a next or prev link is clicked, the search parameters
// are not lost, so load a var with what is in the $_GET.
if(isset($_GET['page'])){
//if page is already there, dont need to add anything to the uri till later
$get_search = $_SERVER['REQUEST_URI'];
}else{
if(!strpos($_SERVER['REQUEST_URI'], "?")){ //need to check if "?" is in the uri
//if not, add it
$get_search = $_SERVER['REQUEST_URI']."?page=".$current_page;
}else{
//if so, use some glue
$get_search = $_SERVER['REQUEST_URI']."&page=".$current_page;
}
}
//lets get down to making some links
for ($i=1; $i<=$number_of_pages; $i++){
if($i == 1){ // start with PREV button
if ($current_page <= 1){
$prev_number = 1;
$this->page_nav = "";
}else{
$prev_number = $current_page - 1;
$this->page_nav = "<a href='".str_replace("page=".$current_page, "page="
.$prev_number, $get_search)."' class='prev' "
."title='Previous'> </a> ";
}
}
if($i == $current_page) { // bold current page
$this->page_nav .= "<strong>".$i."</strong> ";
}else{
$this->page_nav .= "<a href='".str_replace("page=".$current_page, "page="
.$i, $get_search)."'>".$i."</a> ";
}
if($i == $number_of_pages){ // end with NEXT button
if ($current_page >= $number_of_pages){
$next_page = $number_of_pages;
}else{
$next_page = $current_page + 1;
$this->page_nav .= "<a href='".str_replace("page=".$current_page, "page="
.$next_page, $get_search)."' class='next' title='Next'>"
." </a>";
}
}
}
}
// set starting point for database query
if ($current_page == 1) { $start_row = 0; }
else { $start_row = NUM_ROWS * ($current_page-1); }
// now, get only rows to be displayed
$this->limits = " LIMIT ".$start_row.", ".NUM_ROWS;
}
//makes a string about the search info, query time, num results, etc.
function make_display_q(){
switch ($this->mode){
//we're searching
case 1:
if($this->num_rslts==1){$match='match';}else{$match='matches';}
$this->make_boolean();
$this->display_q = "Looking in fields ".SEARCH_FIELDS." for "
."<strong>'".$this->string."'</strong> "
.strtolower($this->boolean)."<br />"
."and found ".$this->num_rslts." $match for your "
."search criteria in ".$this->time." seconds";
$this->make_boolean(); //put boolean back
break;
//we're not searching
case 2:
$this->display_q = "";
break;
}
}
}
two things im concerned about: is there a better way to deal with the boolean thing? i don't like the fact that i have to call the function twice every time i need to implement it once, but i was trying to stay away from adding another var. i guess i could make it return " IN BOOLEAN MODE" or "" based on true/false, but i don't know. the other thing is the function make_pg_nav(), do you think that is optimized? how heavy do you think it is? average search object time for a loop 10000 strong was 0.019365 s; not sure how that falls though.... any other constructive comments are welcome. thanks!