【问题标题】:Filters not working (Executing different PHP queries based on parameters)过滤器不起作用(根据参数执行不同的 PHP 查询)
【发布时间】:2016-04-10 00:11:50
【问题描述】:

我正在尝试实现过滤器,这将帮助用户优化那里的其他用户搜索。这是我的搜索参数的图像,只是为了向您提供我将很快传达的图形表示:

共有三个过滤器:

  1. 性别
  2. 年龄
  3. 研究中的相似性

默认情况下,我想传达系统上的所有用户。因此,当用户进入users.php 时,将显示每个用户,然后,当应用过滤器时,相应地优化结果。

并非所有三个参数都必须完成才能开始搜索,例如,用户可以简单地搜索女性用户,它应该在搜索点击时显示所有女性用户。

我尝试为每个场景实现不同的查询,但始终显示所有用户。如果我指定我要搜索女性,然后单击搜索,它不会执行任何操作,仍会显示所有用户。

另外,我正在努力使用similarity in studies 参数。它的工作方式是在一个名为user_bio 的表中存储有关用户正在学习的数据,用户可以选择不提供此信息,因此我的表中的学习也可以是空的。

我希望它的工作方式是查看登录用户正在学习的内容,然后在其他人的简历中找到匹配的单词。例如,我目前以 Conor 身份登录,而 Conor 正在学习计算机科学。理想情况下,将运行一个算法,从user_bio 表中搜索其他用户的简历,并返回所有在他们的简历中拥有computerscience 的用户。我很确定这与LIKE 子句有关,但我以前从未使用过它,所以我不能确定。

这是我目前的做法:

// processing filters
$refined_gender = htmlentities (strip_tags(@$_POST['gender']));
$age_from       = htmlentities (strip_tags(@$_POST['age_from']));
$age_to         = htmlentities (strip_tags(@$_POST['age_to']));
$studying       = htmlentities (strip_tags(@$_POST['studying']));

$get_all_users = mysqli_query ($connect, "SELECT * FROM users" );

mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);

if (isset($_POST['submit'])){   
        // if gender parameter is used ...
        if ($refined_gender){
                $gender_statement = mysqli_prepare ($connect, "SELECT * FROM users WHERE gender = ?");
                mysqli_stmt_bind_param($gender_statement, "s", $refined_gender); 
                mysqli_stmt_execute ($gender_statement);
                mysqli_stmt_close($gender_statement);
        }
        // if studying parameter used...
        if ($studying) {
            // see explanation below...
        }
        // if gender and age parameter used...
        if ($refined_gender && $age_from && $age_to){
                $gen_and_age_statement = mysqli_prepare ($connect, "SELECT * FROM users WHERE gender = ? AND age BETWEEN ? AND ?");
                mysqli_stmt_bind_param($gen_and_age_statement, "sss", $refined_gender, $age_from, $age_to); 
                mysqli_stmt_execute ($gen_and_age_statement);
                mysqli_stmt_close($gen_and_age_statement);
        }
}

总结,我需要什么:

  1. SELECT * FROM users 查询默认在users.php 上执行。这将显示系统中的所有用户。
  2. 适用于任何要应用的过滤器。并非所有过滤器都需要应用才能获得结果,用户可以搜索女性并点击搜索,加载系统中的所有女性用户。
  3. 我需要根据应用的过滤器更改查询。所以如果一个用户搜索了一个男性用户,而其他两个选项没有选择,那么查询将是"SELECT * FROM users WHERE gender = '$var_here'

【问题讨论】:

  • 我建议不要隐藏错误消息,而是处理它们。大多数都可以通过简单的 isset() 解决;
  • if ($refined_gender && $age_from && $age_to) 为什么要检查这些变量是否为真?
  • @EdwardBlack - 我的思考过程是“如果字段中有数据,或者选择了选项,请执行此操作...”。
  • 那么你必须使用isset 例如if (isset($refined_gender) && isset($age_from) && isset($age_to))

标签: php mysql post sql-like


【解决方案1】:

我在这里提供了代码,这样您如何在单个查询中编写多个过滤器选项..但是在这里我没有提到您正在研究的第三个过滤器选项,因为它是关于另一个表的,并且您没有清楚地提到它这样它使用外键或遵循关系数据库结构链接到此表。任何方式的多过滤器选项如下..这里我添加了数据库连接和转义注入的功能...如果你不需要那忽略那部分..

function escape($e_string)
{
    global $connect;
    if(!isset($connect))
    {
        // DATABASE CONNECTION QUERY
        $connect = mysqli_connect("servername", "username", "password", "");
        if (!$connect) 
            die("Connection failed: " . mysqli_connect_error());
    }   
    $e_string = trim(utf8_encode($e_string));   
    $e_string = mysqli_real_escape_string($connect,$e_string);
    return $e_string;   
}

// processing filters
$refined_gender = isset($_POST['gender']) ? escape($_POST['gender']) : '';
$age_from       = isset($_POST['age_from']) ? escape($_POST['age_from']) : '';
$age_to         = isset($_POST['age_to']) ? escape($_POST['age_to']) : '';
$studying       = isset($_POST['studying']) ? escape($_POST['studying']) : '';

$query = "SELECT * FROM users WHERE 1=1";

if (isset($_POST['submit'])){

        $addstring1 = $addstring2 = $addstring3 =  $and1 = $and2 = $and3 = "";
        $andcnt =3;

        if($refined_gender != '') 
        $addstring1 = " gender = '$refined_gender'";    

        if($age_from != '') 
        $addstring2 = " age >= '$age_from'";    

        if($age_to != '') 
        $addstring3 = " age <= '$age_to'";    

        for($i=1;$i<=$andcnt;$i++)
        ${"and".$i} =  ${"addstring".$i} != '' ? " AND" : "";

        $query .= $and1.$addstring1.$and2.$addstring2.$and3.$addstring3;

}
$get_all_users = mysqli_query ($connect, $query);
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);

【讨论】:

  • 嗨。我在所有四个processing filters 上都收到了Fatal error: Call to undefined function escape() 错误。知道为什么吗?在那之前,我无法测试这个方法
  • 您是否复制并粘贴了我在上面写的相同功能“转义”?...这是为了确保您的输入安全。检查查询是否正常工作或不从 POST 输入中删除所有转义功能调用..
  • 是的,你介意加入这个聊天室,所以我可以就你的方法问你几个问题吗? chat.stackoverflow.com/rooms/109252/php-filters
【解决方案2】:

代替这段代码:

 htmlentities (strip_tags(@$_POST['gender']));

你应该像这样验证它:

 $gender = filter_input(INPUT_POST, 'gender', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '/^[mf]$/i']]);
 $ageFrom = filter_input(INPUT_POST, 'age_from', FILTER_VALIDATE_INT, [ 'default' => 1, 'min_range' => 1, 'max_range' => 100]);
 $ageTo = filter_input(INPUT_POST, 'age_to', FILTER_VALIDATE_INT, [ 'default' => 1, 'min_range' => 1, 'max_range' => 100]);
 $studying = filter_input(INPUT_POST, 'gender', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '/^(similar|different|same)$/i']]);

这样更简单、更安全。

应正确验证每个输入。

避免使用 @

一旦有了值,就可以将它们连接到查询中,如下所示:

$types = '';
$values = [];
$query = 'SELECT * FROM users';
$where = [];

// empty tests for both null (no data in input) and false (invalid data)
if (!empty($gender)) {
    $where[] = 'gender = ?';
    $types .= 's';
    $values[] = &$gender;
}
if (!empty($ageFrom)) {
    $where[] = 'age >= ?';
    $types .= 'i';
    $values = &$ageFrom;
}
if (!empty($ageTo)) {
    $where[] = 'age <= ?';
    $types .= 'i';
    $values = &$ageTo;
}
if (!empty($studying)) {

    $field = 'user_bio';

    // Get the $user_bio value of the current user from the database

    // Change the $user_bio into a regular expression collection of words
    $regexp = '('.str_replace(' ','|',$user_bio).')';

    // Set up the where 
    switch ($studying) {
        case 'same':
            $comparison = '= ?';
            break;
        case 'different':
            $comparison = 'NOT REGEXP (?)';
            break;
        case 'similar':
            $comparison = 'REGEXP (?)';
            break;                
    }
    $where[] = $field.' '.$comparison;
    $types .= 's';
    $values[] = &$user_bio;
}

if (count($where) > 0) {
    $query .= ' WHERE '.implode(' AND ',$where);
}

// new mysqli ( host, 
$mysqli = new mysqli('localhost','root','','stuff');
$stmt = $mysqli->prepare($query);

// This allows you to use a variable number of arguments with the prepared statement
// Note the use of the ampersands on the array assignment, this ensures they are passed by reference
$params = array_merge([$types],$values);
call_user_func_array([$stmt,'bind_param'],$params);

$stmt->execute();

// Bind a variable for each column
$stmt->bind_result($user_name);

while ($stmt->fetch()) {
var_dump($result);
}

【讨论】:

  • 明白。你在哪里有mf,这些只是例子吗?即我应该用每个单选按钮(['male', 'female'])的value 替换它们吗?也很抱歉回复晚了,真的很忙,感谢您的帮助:)
  • 是 - 例如
  • 我在使用您的方法验证年龄时遇到了几个问题。看到年龄过滤器依赖于两个值($age_from$age_to)我如何验证这一点?目前,我有以下$ageFilter = false; if (isset($_POST['gender'])) { $age_from = strtolower($_POST['age_from']); $age_to = strtolower($_POST['age_to']); if (!in_array($age_from, $age_to,['age_from','age_to'])) die; $ageFilter = true; },我是 100% 我犯了一个错误。另外,在处理 $ageFilter 时,如何设置为 $value?我有$values[] =&amp;$age_from, $age_to;?
  • 嗯,我在这个问题上卡住了一段时间,但是我得到了一个`未定义的变量`错误和`在非对象上调用成员函数 prepare()`的致命错误行$stmt = $mysqli-&gt;prepare($query);,我不知道为什么?
  • 对 mysql 使用面向对象的表示法 - php.net/manual/en/class.mysqli.php
【解决方案3】:

(我不确定为什么已经提供的答案没有充分解决您的问题。)

我会这样处理。首先,摆脱第一个查询执行以拉取所有用户。相反,只使用一个查询。

动态准备 SQL 文本。以“SELECT ... FROM users”开始语句。 (我们将处理附加一个 ORDER BY 作为最后一步。

我会有条件地检查每个“过滤器”,看看我是否需要在 WHERE 子句中附加一个条件。

在 SQL 的开头,我们将包含一个“WHERE 1=1”。

$sql = "SELECT ... FROM users u" 
$sql .= " WHERE 1=1";

“WHERE 1=1”基本上没用。优化器将把它扔掉。我们添加它的原因只是为了让我们以后的代码更容易。我们可以在下一个过滤器后面加上“AND 条件”,不用担心这是否是第一个过滤器,我们需要使用 WHERE 而不是 AND。

我们将初始化一个字符串和一个数组,以保存我们需要的绑定类型字符串“sssis”,以及一个对我们想要传入的值的引用的数组。

  $bind_type = "";
  $bind_vals = array();

每个过滤器的处理都会很麻烦......但我们可以做到。检查我们是否需要在 SQL 中附加任何内容。如果我们这样做,找出需要添加的内容,包括任何绑定占位符。并将绑定参数的类型(“i”、“s”等)附加到 $bind_type 字符串,并将一个值(对的引用)推送到我们的 $bind_vals 数组中。

if ( $refined_gender ) {
   // figure out what that SQL text needs to look like
   // append the string to the SQL text
   $sql .= " AND u.gender = ?";

   // append type to string, and push a reference to the value into array 
   $bind_types .= "s";
   $bind_val[] = &$refined_gender;
}

我们的代码会比这更复杂。那只是处理相等比较。我们现在只是保持简单,以说明模式。

我们对可能需要添加的每个过滤器重复相同的操作。检查是否需要,找出我们需要附加到 SQL 文本的内容,附加到 bind_types 字符串并将值推送(引用)到 bind_vals 数组中。

为了解决这个问题,我会从只处理一个条件开始,然后让它发挥作用,从而解决问题。当我们添加更多过滤器并且事情出错时,我知道在哪里寻找问题。 (我知道之前的工作原理。)

当我用完 WHERE 子句后,我会附加任何我需要的 ORDER BY 和 LIMIT。这可能是有条件的,但最终,我们会做这样的事情:

   $sql .= " ORDER BY u.id DESC LIMIT 50";

当我完成所有这些后,我得到了一个包含 SQL 文本的字符串,看起来像这样:

  SELECT ...
    FROM users u 
   WHERE 1=1
     AND u.gender = ?
     AND u.age_from >= ?
     AND u.age_to   <= ?
   ORDER BY u.id DESC
   LIMIT 50

(在这个例子中,它包含三个绑定占位符。如果我们做得对,

我们将有一个包含三个字符的 $bind_types 字符串,例如“sii”

我们将有一个 $bind_vals 数组,其中包含对三个值的引用。

现在,我们可以调用 mysqli_stmt_prepare。如果我们的 SQL 没有错误,我们应该取回一个语句句柄。

$stmt = mysqli_prepare($conn,$sql);

(检查prepare的返回值。)

现在我们只需要绑定我们的参数。这就是 mysqli 让事情变得有点棘手的地方。如果我们使用 PDO(或 Perl DBI),调用“绑定参数/绑定值”会很容易。这些将让我们传递绑定值的数组。但不是mysqli。他不会让我们以数组作为参数调用 mysqli_stmt_bind_param。

我们需要像这样运行一个函数调用:

mysqli_stmt_bind_param($stmt, $bind_types, &$refined_gender, &$age_from, ... );

我们的问题是我们有一个可变个参数。

有一个解决方法。

我们可以使用call_user_func_array函数。

因为代码使用过程风格而不是面向对象风格,所以准备好的语句的句柄是第一个参数,第二个参数是绑定类型字符串,然后是绑定值。绑定值已经在数组中。我们只需要将所有这些都放入一个 hugh jass 数组中。

array_merge 函数似乎是为此而定制的。

 // array_merge(array($stmt), array($bind_types), $bind_vals)

这将返回一个数组。这正是我们调用call_user_func_array 函数所需要的。我们在其他任何地方都不需要该数组(除非我们正在调试,并且我们想将其打印出来)。

如果我们的语句中至少有一个绑定占位符,我们只需要调用 mysqli_stmt_bind_param。因此,如果我们的 $bind_types 字符串为空,我们可以绕过它。 (而且我们知道 $bind_types 不会是“0”,因为我们的代码从未将“0”附加到它上面。)

 if ($bind_types) {
    call_user_func_array('mysqli_stmt_bind_param', array_merge(array($stmt), array($bind_types), $bind_vals) );
 }

第一个参数(call_user_func_array)是我们要执行的函数的名称,第二个参数是我们要转换成列表的hugh jass数组。

这样做的重点是让它动态化,我们可以传入一个、两个、三个绑定值。

此时,我们已准备好执行语句并获取结果。

再次指出,重要的是:mysqli_stmt_bind_param 期望绑定值通过引用,而不是通过值。这就是为什么我们将 references 推送到 bind_vals 数组中的值。

我不确定你问了什么问题。

但绝对放弃第一次调用 mysqli_query。这将返回 users 表中的所有行。

只有一两个条件,静态SQL和静态绑定类型,列出绑定值的方法是可行的。

但是当我们得到三个、四个、五个可能的过滤器以及所有可能的组合时,这将是笨拙的。

所以我们采用更动态的方法,动态创建查询,并将绑定值推送到数组上。

【讨论】:

    【解决方案4】:

    这个 Html 页面:

    <form method="POST" action="">
        <input type="radio" name="rbo_gender" value="male">Male
        <input type="radio" name="rbo_gender" value="female">Female
        Age From<select name="agefrom">
            <?php
            for($i=10;$i<50;$i++):
            ?>
            <option value="<?php echo $i?>"><?php echo $i?></option>
            <?php
            endfor;
            ?>
        </select>
        Age To<select name="ageto">
            <?php
            for($i=10;$i<50;$i++):
            ?>
            <option value="<?php echo $i?>"><?php echo $i?></option>
            <?php
            endfor;
            ?>
        </select>
        Studying:
        <input type="radio" name="rbo_type" value="similar">Similar
        <input type="radio" name="rbo_type" value="exact">Exactly same
        <input type="radio" name="rbo_type" value="different">Different
        <input type="submit" name="btnsearch" value="Search">
    </form>
    

    这是php部分:

    if($_POST["btnsearch"])
    {
       if(!empty($_POST["rbo_gender"]))
       {
           $gender = $_POST["rbo_gender"];
           $cond .= " and gender = '".$gender."'";
       }
       if(!empty($_POST["agefrom"]))
       {
           $agefrom = $_POST["agefrom"];
           $cond .= " and age >= '".$agefrom."'";
       }
       if(!empty($_POST["ageto"]))
       {
           $ageto = $_POST["ageto"];
           $cond .= " and age <= '".$ageto."'";
       }
       if(!empty($_POST["rbo_type"]))
       {
           $user_type = $_POST["rbo_type"];
           switch($_POST["rbo_type"])
           {
               case "similar": $cond .= " and user_bio like '%".$ageto."%'";
                   break;
               case "exact": $cond .= " and user_bio = '".$ageto."'";
                   break;
               case "different":$cond .= " and user_bio ! like '%".$ageto."%'";
                   break;       
           }
       }
    
       $query = "select * from users where 1 ".$cond;
    }
    

    请按照 mysqli() 更新查询并使用绑定参数。另外,不要使用@,尝试使用filter_input,您也可以使用REGEXP,而不是like。我创建了变量以使用 bind_param 目的。

    【讨论】:

    • 您好,很抱歉回复晚了。我在改变它的要求后尝试了你的方法。但是当我点击搜索时,什么也没有发生,它只是刷新了页面?目前,让我们只考虑性别过滤器。我有` $query = mysqli_query ($connect, "SELECT * FROM users WHERE account_type = 'user'".$cond);` 作为我的查询,当我从单选选项中选择男性时,它仍然向我显示所有用户?
    • 此处列出的代码似乎易受 SQL 注入攻击。在这个时代,没有理由不使用带有绑定占位符的准备好的语句,或者至少正确地转义包含在 SQL 文本中的潜在不安全值。这个答案中的方法可能“有效”。但在安全性方面,它获得了 FAIL 的评分。
    • @spencer7593,这里我只添加了他的概念并且已经提到我们必须使用 bind_param 和所有其他安全预防措施......
    • @Freddy,请尝试打印“SELECT * FROM users WHERE account_type = 'user'”。$cond 它会给你准确的查询。
    猜你喜欢
    • 2014-10-10
    • 2016-09-13
    • 2021-08-14
    • 2017-10-30
    • 2021-06-12
    • 1970-01-01
    • 2015-01-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多