DBクラスと検索機能(PHP)
ようやくPHPのオブジェクト指向がわかってきた今日この頃、ワンワード(one word)ではあるが、フリーワード検索のPHPを組んでみた。 以前からずっと考えていたことなのだが、案外簡単にできそうな方法を思いついた。正規表現、文字のパターンマッチングを利用して検索を行う。それには検索対象が必要だ。私が作ったDBクラスはDBへの接続を実体としてもち、クエリ(SQL)を送信することでその結果を2次元配列として戻すというもの。今回作った検索システムはこの2次元配列を利用する。
まずDBクラスだがオープンソース2種類、MySQLとPostgreSQLに対応したものを用意した。
接続情報は別ファイルで管理する。以下は、接続情報を格納するためのconfig.phpの内容である。
<?php
//DATABASE CONNECTION PARAMETERS
$hostname = "localhost";
$database = "user01";
$username = "user01";
$password = "xxxxxxxx";
?>
そして以下はDBクラス。
まずはMySQL。
<?php
class DbMySQL{
private $hostname;
private $database;
private $username;
private $password;
public function __construct($hostname, $database, $username, $password){
if ($hostname == "" || $database == "" || $username == "" || $password == ""){
print "<strong>WARNING from DbMySQL class:</strong> Set proper hostname, database name, username and password for your database in config.php. If your database server doesn't support any of these parameters, this class is not effective.";
exit();
}else{
$this->hostname = $hostname;
$this->database = $database;
$this->username = $username;
$this->password = $password;
}
}
//setter
public function __set($key, $value){
$this->$key = $value;
}
/* データベースへの接続、データ抽出、切断を行う */
public function mysql($query){
/* 接続・選択・領域確保・抽出・領域開放・切断 */
$link = mysql_connect($this->hostname, $this->username, $this->password) or die("DATABASE CONNECTION ERROR!<br />");
mysql_select_db($this->database, $link) or die("DATABASE SELECTION ERROR!<br />");
$result = mysql_query($query);
if (!$result){
print "DATABASE QUERY ERROR!<br />";
print mysql_error()."<br />";
}else{
/* 2次元配列にテーブルデータを格納 */
$resultData = array();
for ($i = 0; $i < mysql_num_rows($result); $i++){
$resultData[$i] = array();
for ($j = 0; $j < mysql_num_fields($result); $j++){
$resultData[$i][$j] = mysql_result($result, $i, $j);
}
}
}
mysql_free_result($result);
mysql_close($link);
return $resultData;
}
}
そしてPostgreSQL。
class DbPgSQL{
private $connStr;
public function __construct($hostname, $database, $username, $password){
if ($hostname == "" || $database == "" || $username == "" || $password == ""){
print "<strong>WARNING from DbPgSQL class:</strong> Are you really set proper hostname, database name, username and password for your database? If your database server doesn't support any of these parameter, this class is not effective.";
exit();
}else{
$this->connStr = "host=".$hostname." dbname=".$database." user=".$username." password=".$password;
}
}
//setter
public function __set($key, $value){
$this->$key = $value;
}
/* データベースへの接続、データ抽出、切断を行う */
public function postgresql($query){
/* 接続・領域確保・抽出・領域開放・切断 */
$link = pg_connect($this->connStr) or die("DATABASE CONNECTION ERROR!<br />");
$result = pg_query($query);
if (!$result){
print "DATABASE QUERY ERROR!<br />";
print pg_last_error()."<br />";
}else{
/* 2次元配列にテーブルデータを格納 */
$resultData = array();
for ($i = 0; $i < pg_num_rows($result); $i++){
$resultData[$i] = array();
for ($j = 0; $j < pg_num_fields($result); $j++){
$resultData[$i][$j] = pg_fetch_result($result, $i, $j);
}
}
}
pg_free_result($result);
pg_close($link);
return $resultData;
}
}
次は検索クラスの作成だが、今回考えたアルゴリズムはこうだ。
2次元配列の1次元になっている部分をすべて連結してひとつの文字列にする。この文字列を正規表現を使って判別し、キーワードが入っていれば結果用の2次元配列に格納する、というもの。2重ループの内側のループが2回登場してしまうので、効率はどうなのかとも思うがひとまずできたので掲載してみようと思う。
class FWSearch{
private $outerArray;
private $resultArray = array();
public function __construct($outerArray){
if ($this->isArray($outerArray)){
$this->outerArray = $outerArray;
}else{
print "Searching Subject Error [Parameter variable is not array.] - index.php (line: 87)";
}
}
public function search($searchKw){
$rePattern = "/".$searchKw."/i"; // Create regular expression string
for ($i = 0; $i < count($this->outerArray); $i++){
$dump = ""; // Prepare temporary variable to keep one-row string
for ($j = 0; $j < count($this->outerArray[$i]); $j++){ // Create searched one-row strings
$dump .= $this->outerArray[$i][$j]; // This process creating one string connect all the field of array.
}
if (preg_match($rePattern, $dump)){ // Pattern matching by regular expression
$this->resultArray[$i] = array();
for ($j = 0; $j < count($this->outerArray[$i]); $j++){
$this->resultArray[$i][$j] = $this->outerArray[$i][$j];
}
}
}
if ($this->isArray($this->resultArray)){
print "RESULT OF SEARCH: <strong>".count($this->resultArray)." record".$this->enPlural(count($this->resultArray), "s")." found!</strong><br />";
}else{
print "RESULT OF SEARCH: No match for keyword: <strong>\"".$searchKw."\"</strong><br />";
}
return $this->resultArray;
}
/* isArray method easily checks given parameter is array or not. */
public static function isArray($array){
$num = count($array);
if ($num != 0){
return true;
}
return false;
}
/* enPlural easily returns "s" or "es" to add the end of noun */
public static function enPlural($num, $form){
if ($num > 1){
return $form;
}
return "";
}
}
さてこれらを実際に使うときだが、MySQLを例に生成手順を記しておこう。DBクラスは両方とも同じ使い方であるので問題ない。検索クラスも簡単に利用できるようにしたつもり。結果はすべて2次元配列なので気をつけておこう。
/* 確認用簡易テーブルタグ生成関数 */
function makeSimpleTable($array2D){
$table = "<table border=\"1\">\n";
for ($i = 0; $i < count($array2D); $i++){
$table .= "\t<tr>\n";
for ($j = 0; $j < count($array2D[$i]); $j++){
$table .= "\t\t<td>".$array2D[$i][$j]."</td>\n";
}
$table .= "\t</tr>\n";
}
$table .= "</table>\n";
return $table;
}
/* 共通で使える処理と変数 */
require_once "./config.php";
$query = "SELECT * FROM test";
/* DbMySQL サンプルコード */
$mysql = new DbMySQL($hostname, $database, $username, $password);
$data1 = $mysql->mysql($query);
print makeSimpleTable($data1);
print "<br /><br />";
/* FWSearch サンプルコード DbMySQL使用 */
$search1 = new FWSearch($data1);
$rData1 = $search1->search("80");
print_r($rData1);
print "<br /><br />";