【问题标题】:Empty resultsset returned when using mysqli::bindParams()使用 mysqli::bindParams() 时返回空结果集
【发布时间】:2022-01-25 11:11:53
【问题描述】:

对于我当前的项目,我正在构建一个简单的 QueryBuilder 类。一切正常,除了与 mysqli 的 bindParam() 方法的参数绑定,我就是找不到错误。

代码和程序:

  • 请求处理程序从我的用户模型中调用以下方法:

    public function getPasshashByUsername($username) {
      $qb = new SelectQuery();
      $qb->select(['passhash'])
         ->from($this->sourceName)
         ->where('nickname', $username);
      $stmt = $qb->build();
      $resultset = $qb->execute($stmt);
      return $resultset->fetch_assoc()['passhash'] ?? null;
    }
    
  • 对于具有潜在参数的部分,SelectQuery 类的工作方式如下:

    public function where($col, $val) {
      $this->aQueryParts['where'] = $this->addCondition('WHERE',$col, $val);
      return $this;
    }
    
    private function addCondition($type, $col, $val) {
      $sExpr = $type . ' ';
      $sType = '';
      if ( is_int($val) || is_bool($val) ) {
        $sExpr .= '? = ' . $val;
        $sType .= 'i';
      } elseif ( is_double($val) ) {
        $sExpr .= '? = ' . $val;
        $sType .= 'd';
      } else {
        $sExpr .= '? LIKE \'' . $val . '\'';
        $sType .= 's';
      }
      $this->sTypes .= $sType;
      $this->aParams[] = $col;
      return $sExpr;
    }
    
  • 现在 build() 方法开始起作用,参数将被绑定:

    public function build() {
      if ( isset($this->aQueryParts['from']) ) {
      $sQuery = '';
      foreach ( $this->aQueryParts as $part ) {
        if ( is_string($part) ) {
          if ( !empty($part) ) {
            $sQuery .= $part . ' ';
          }
          continue;
        }
        if ( is_array($part) ) {
          foreach ( $part as $entry ) {
            if ( !empty($entry) ) {
              $sQuery .= $entry . ' ';
            }
          }
        }
      }
      $this->sQuery = $sQuery;
      // Marker 1
      //$this->sQuery = 'SELECT * FROM vwuser WHERE nickname LIKE \'TestUser\'';
      } else {
        Logger::send('You must at least call from() to get a valid query.');
        die();
      }
      $this->con = (new DatabaseAdapter())->connect();
      if ( !$stmt = $this->con->prepare($this->sQuery) ) {
        $msg = 'Error while preparing statement: "' . $this->sQuery . '"';
        Logger::send($msg);
        die();
      }
      // make params by-ref
      $params = [];
      foreach ( $this->aParams as $param ) {
        $params[] = &$param;
      }
      // Start Marker 2 
      if ( !empty($this->sTypes) ) {
        if ( !$stmt->bind_param($this->sTypes, ...$params) ) {
          $msg = 'Failed to bind parameters to Query: "' . $this->sQuery . '"';
          Logger::send($msg);
          die();
        }
      }
      // End Marker 2
      return $stmt;
    }
    

execute() 方法只是以通常的方式包装 mysqli execute()。没有抛出错误/异常,它只返回一个没有匹配的结果集。关于 XDebug,这些是调用 bind_params() 时的相关值:

$this->sQuery = "SELECT passhash FROM vwuser WHERE ? LIKE 'TestUser'";
$this->sParams = "s";
$this->aParams = [ "nickname" ];

如果我在“标记 1”下面的行中取消注释查询字符串的硬编码分配并注释掉标记 2 的块(调用 bind_params),一切正常,所以看起来连接本身是有效的。 在 php.net 上阅读 bind_params 需要引用后,我插入了“make by-ref”块,但它没有改变任何东西。

【问题讨论】:

  • 'nickname' 怎么可能匹配 'TestUser'?这两个是完全不同的字符串。
  • 我不明白你为什么要使用 MySQLi。这只是 PDO 的几行代码。
  • @Dharman 昵称是 vwuser 中的 col 名称
  • 它将值 'TestUser' 与 vwuser.nickname 的实际值进行比较,就像在标记 1 的工作查询中一样 -> "SELECT passhash FROM vwuser WHEREnickname LIKE 'TestUser'"
  • 哦,好吧,我想我有点搞糊涂了 =D 休息时间

标签: php mysqli


【解决方案1】:

你真的应该考虑学习 PDO 而不是 mysqli 或使用一些现有的数据库访问库。

您不能绑定列名。关键是你绑定数据。你正在做相反的事情。我建议摆脱addCondition 方法,因为它没有任何意义,并且会导致代码中出现许多错误。相反,只需将参数直接传递到 bind_param 并使用 str_repeat() 作为类型。

你可以这样做:

public function where($col, $val)
{
    $this->aQueryParts['where'] = 'WHERE '.$col. '=?';
    $this->aParams[] = $val;
    return $this;
}

public function build()
{
    if (isset($this->aQueryParts['from'])) {
        $sQuery = '';
        foreach ($this->aQueryParts as $part) {
            if (is_string($part)) {
                if (!empty($part)) {
                    $sQuery .= $part . ' ';
                }
                continue;
            }
            if (is_array($part)) {
                foreach ($part as $entry) {
                    if (!empty($entry)) {
                        $sQuery .= $entry . ' ';
                    }
                }
            }
        }
        $this->sQuery = $sQuery;
    // Marker 1
    //$this->sQuery = 'SELECT * FROM vwuser WHERE nickname LIKE \'TestUser\'';
    } else {
        Logger::send('You must at least call from() to get a valid query.');
        die();
    }

    $this->con = (new DatabaseAdapter())->connect();
    $stmt = $this->con->prepare($this->sQuery);
    // Start Marker 2
    if ($this->aParams) {
        $stmt->bind_param(str_repeat('s', count($this->aParams)), ...$this->aParams);
    }

    // End Marker 2
    return $stmt;
}

如您所见,我还删除了if 语句。你真的应该考虑enabling mysqli error reporting 而不是手动检查错误。

【讨论】:

  • 感谢您提出的解决方案,我会考虑的。如果用户在 DatabaseProvider 的设置中手动关闭 mysqli_error_reporting,这只是最小的异常处理。但我正在考虑消除这种可能性,因为它似乎没有任何好处
【解决方案2】:

这里是更正的版本。 感谢@Dharman 给我提示。 错误是在行中的问号位置有些混乱:

$sExpr .= '? LIKE \'' . $val . '\'';

当然应该是:

$sExpr .= $col . ' LIKE ?';

所以 addCondition() 的修正版本是:

  private function addCondition($type, $col, $val) {
    $sExpr = $type . ' ';
    $sType = '';
    if ( is_int($val) || is_bool($val) ) {
      $sExpr .=  $col . ' = ?';
      $sType .= 'i';
    } elseif ( is_double($val) ) {
      $sExpr .=  $col . ' = ?';
      $sType .= 'd';
    } else {
      $sExpr .= $col . ' LIKE ?';
      $sType .= 's';
    }
    $this->sTypes .= $sType;
    $this->aParams[] = $val;
    return $sExpr;
  }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-14
    • 1970-01-01
    • 2014-08-11
    • 1970-01-01
    相关资源
    最近更新 更多