啊,搜索引擎。令人兴奋的主题,但我宁愿用内部智能构建一些东西,而不是使用蛮力解决方案。是的 - 检查数据库中的每个表/列是蛮力的,可能会导致反应迟缓和误报。
让我向您介绍一些我会使用的东西。使用以下解决方案需要手动添加每个值得扫描的表/列,但其他一切都是自动的。用法如下:
$e = new SearchEngine();
$e->addTable('users', 'id', 'login'); // table, primary key name, column to be searched in
$e->addTable('users', 'id', 'last_name');
$e->addTable('towns', 'id', 'name');
print_r($e->search('austin')); // we search for exact match for word "austin"
以下是它的实现方式:
class SearchEngine {
protected $tables = array();
public function addTable($table, $key, $column) {
$this->tables[] = array(
'table' => $table,
'key' => $key,
'column' => $column
);
}
public function search($term) {
$q = array();
foreach ($this->tables as $t) {
list($table, $key, $column) = $t;
$q[] = "
SELECT
$key AS searched_key,
'$key' AS searched_key_name,
'$table' AS searched_table,
'$column' AS searched_column,
$column AS searched_value
FROM $table
WHERE $column = $term
";
}
$sql = implode(' UNION ', $q);
// query the database
// return results
}
} // class SearchEngine
让我们分析示例输出:
searched_key | searched_key_name | searched_table | searched_column | searched_value
-------------+-------------------+----------------+-----------------+---------------
276 | id | users | login | austin
1782 | id | users | last_name | austin
71 | id | towns | name | austin
从上表中,您可以找出短语“austin”在表users、列login(主键276)和列last_name(主键1782)中找到。在name列(主键71)的表towns中也发现了它;
这样的搜索结果可能对您来说已经足够了。否则,您可以进一步处理列表以从每个表中选择整行:
$out = array();
foreach ($rows as $row) {
$sql = "
SELECT * FROM {$row['searched_table']}
WHERE {$row['searched_key_name']} = {$row['searched_key']}
LIMIT 1
";
// query the database
// append result to $out array
}
return $out;
这样您将获得完整的搜索结果(与上表的中间结果相反):
id: 276, login: austin, last_name: Powers, email: austin.powers@gmail.com
id: 1782, login: michael, last_name: austin, email: michael.e@gmail.com
id: 71, name: austin, state: texas, country: usa
由于当前的实现仅限于固定比较运算符(WHERE 字段 = 值),因此您可能希望在此处引入一些灵活性。如果是这样,则需要将搜索运算符委托给外部类并注入search()函数:
public function search(SearchOperator $operator, $term) {
...
然后SearchOperator 需要考虑通过将 WHERE 条件替换为以下内容:
WHERE {$operator->toSQL($column, $term)}
现在让我们关注SearchOperator 的实现。由于操作符实现只提供了一种方法,即toSQL,我们不需要完整的类,甚至不需要抽象类。在这种情况下,接口就足够了:
interface SearchOperator {
public function toSQL($column, $term);
} // interface SearchOperator
让我们定义几个代表=(等于)和LIKE运算符的实现:
class Equals implements SearchOperator {
public function toSQL($column, $term) {
return "$column = '$term'";
}
} // class Equals
class Like implements SearchOperator {
public function toSQL($column, $term) {
return "$column LIKE '$term'";
}
} // class Like
当然,任何其他实现都是可能的 - 考虑名为 StartsWith、EndsWith 或 DoesNotContain 的类。
查看更新的解决方案使用情况:
$e = new SearchEngine();
$e->addTable('users', 'id', 'login');
$e->addTable('users', 'id', 'last_name');
$e->addTable('towns', 'id', 'name');
print_r($e->search(new Like(), 'austin%')); // here we search for columns being LIKE 'austin%'
是时候留下一些最后的评论了:
- 以上示例不完整。为清楚起见,省略了数据库查询代码。
- 示例中使用的 SQL 不会清理输入数据。我强烈建议您使用带有绑定参数的预处理语句以避免巨大的安全风险。
- 上面介绍的搜索算法是一种幼稚的算法。可以进行一些优化(即对引用同一个表的查询进行分组)。但不要过早优化 - 等到它成为一个真正的问题。
希望这会有所帮助。