Dynamic select mysqli query with dynamic parameters returns error doesn't match number of bind variables

I'm trying to create a select query with dynamic where clause and dynamic parameters but I always get error :

Warning: mysqli_stmt::bind_param(): Number of elements in type definition string doesn't match number of bind variables

Which I sincerely do not understand since it seems the count is alright. So this is what the code really looks like in its rude format. I can't see what I'm doing wrong.

//get variables
$mediaArray ='Facebook,Twitter,Twitch,';
$otherMedia = 'House';

//convert string to array
$socialArray = explode(',', $mediaArray)

//declare some variables to be used later
$andwhere = '';
$bp = '';
$socialmarray = ''

 //get every value from array of social media
foreach($socialArray as $socialmedia){

    $socialmarray .=$socialmedia.',';
    $andwhere .= " AND socialmedianame=?";
    $bp .='s';
}

//test strings
echo $wheres = $andwhere;//AND socialmedianame=? AND socialmedianame=? AND socialmedianame=?
echo $bip = $bp.'s';//ssss
echo $validarayy = rtrim($socialmarray,',');//Facebook,Twitter,Twitch

//select query
$selectquery = $conn->prepare("select * from mediaservices where socialmedianame=? $wheres");
$selectquery->bind_param("$bip",$otherMedia,$validarayy);
$selectquery->execute();
$resultquery = $selectquery->get_result();

Solution 1:

Because:

  1. You are using user-supplied data, you must assume that your query is vulnerable to a malicious injection attack and
  2. the amount of data that is to be built into the query is variable/indefinite and
  3. you are only writing conditional checks on a single table column

You should use a prepared statement and merge all of the WHERE clause logic into a single IN statement.

Building this dynamic prepared statement is more convoluted (in terms of syntax) than using pdo, but it doesn't mean that you need to abandon mysqli simply because of this task.

$mediaArray ='Facebook,Twitter,Twitch,';
$otherMedia = 'House';

$media = array_unique(explode(',', $mediaArray . $otherMedia));
$count = count($media);

$conn = new mysqli("localhost", "root", "", "myDB");
$sql = "SELECT * FROM mediaservices";
if ($count) {
    $stmt = $conn->prepare("$sql WHERE socialmedianame IN (" . implode(',', array_fill(0, $count, '?')) . ")");
    $stmt->bind_param(str_repeat('s', $count), ...$media);
    $stmt->execute();
    $result = $stmt->get_result();
} else {
    $result = $conn->query($sql);
}
foreach ($result as $row) {
    // access values like $row['socialmedianame']
}

For anyone looking for similar dynamic querying techniques:

  • SELECT with dynamic number of LIKE conditions
  • INSERT dynamic number of rows with one execute() call

Solution 2:

In your query:

$selectquery = $conn->prepare("select * from mediaservices where socialmedianame=? $wheres");

The ? represents one parameter to pass in, and the evaluation of $wheres adds another three, giving you four total parameters.

bind_param() should take a string representing the types of the variables to insert as the first parameter, and the variables themselves as the subsequent parameters.

In your bind:

$selectquery->bind_param("$bip",$otherMedia,$validarayy);

$bip evaluates to ssss and $otherMedia is a single string ("House"). You might expect $validarayy to be three strings, but rtrim() returns a string. Thus, it is only one string ("Facebook,Twitter,Twitch"). You pass through two variables when the query is expecting four:

$conn->prepare("select * from mediaservices where socialmedianame=House AND socialmedianame=Facebook,Twitter,Twitch AND socialmedianame=? AND socialmedianame=? AND socialmedianame=?"

To correct this, you'll want to convert $validarayy back to an array, and use the index for the various inputs:

$socialmarray2 = explode(',', $validarayy);
$selectquery->bind_param("$bip", $otherMedia, $socialmarray2[0], $socialmarray2[1], $socialmarray2[2]);

Also note that your sample code has a few missing semicolons; you'll need to fix these in order for your code to work correctly.

This can be seen working here.

Finally, note that even if you were to split the three strings out correctly, the selection of ... AND socialmedianame=Facebook AND socialmedianame=Twitter AND socialmedianame=Twitch will never match any results; socialmedianame can only contain one value. You're probably looking to substitute your AND statements with OR statements.