for my current project I'm building a simple QueryBuilder class. Everything works fine, except the parameter binding with mysqli's bindParam() method and I just can't find the error.
Code and procedure:
The request handler invokes following method from my User model:
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; }
For parts with potential parameters, the SelectQuery class works like this:
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; }
Now the build() method comes in action, where the params will be bound:
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; }
The execute() method just wraps the mysqli execute() in the usual way. No Error/Exception is thrown, it just returns an result set with no matches. Regarding to XDebug, these are the relevant values when calling bind_params():
$this->sQuery = "SELECT passhash FROM vwuser WHERE ? LIKE 'TestUser'";
$this->sParams = "s";
$this->aParams = [ "nickname" ];
If i uncomment hardcoded assignment of the query string in the line below "Marker 1" and comment out the block of Marker 2 (call of bind_params), everything works fine, so it seems like the connection itself is valid. I've inserted the "make by-ref" block after reading at php.net that bind_params requires references, but it didn't change anything.