Search code examples
phppdobindparam

PDO bindParam not working in loop


I am having trouble getting bindParam to work inside of a foreach loop. If I use bindParam outside of a loop or hardcode the values into the sql query everything works perfectly. According to this page it is suggested to use bindValue instead. However, when I use bindValue it says that the three variables used inside the bindValue are undefined. Which obviously they are at this point. What am I doing wrong?

<?php

    $found_update = false;

    $installed_groups = array(
        array(
            "group_id" => 14,
            "version" => "1.0.7"
        )
    );



    $sql = "select id from testing_set where group_id = :GROUP_ID
        and (
            substring_index(substring_index(version, '.', 2), '.', -1) > :INSTALLED_VERSION_NUM_1 OR
            substring_index(substring_index(version, '.', 3), '.', -1) > :INSTALLED_VERSION_NUM_2
        )
        order by created desc limit 1";

    try {
        $dbh = new PDO("mysql:host=localhost; dbname=".DBNAME, DBUSER, DBPWD);

            $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

            $stmt = $dbh->prepare($sql);

            $stmt->bindParam(":GROUP_ID", $installed_group['group_id'], PDO::PARAM_INT);
            $stmt->bindParam(":INSTALLED_VERSION_NUM_1", $installed_version_parts[1], PDO::PARAM_INT);
            $stmt->bindParam(":INSTALLED_VERSION_NUM_2", $installed_version_parts[2], PDO::PARAM_INT);

            foreach ($installed_groups as $installed_group){

                $installed_version_parts = explode('.', $installed_group['version']);

                $stmt->execute();
                $data = $stmt->fetch(PDO::FETCH_ASSOC);

                if (!empty($data)){
                    $found_update = true;
                    break;
                }
        }

        echo "Found: $found_update\n";

    }
    catch(PDOException $e) {
        http_response_code(404);
        die();

    }

My expected results are for it to display "Found: 1" to the terminal. The way it is now it has a value of false when it should be true.

Solution:

It turns out there were two issues going on here. I have followed IncredibleHat's answer by using basic variables rather than an array in my bindParam. This helped solve the first problem, but the other problem was that I needed to typecast some of data to an int:

$pt1 = (int)$installed_version_parts[1];

I had assumed that PDO::PARAM_INT was doing that for me, but it was not.


Solution

  • Trying to bindParam to an array element like $array['key'] causes a few issues because its bound as reference, but its not. Its, just not done that way.

    So three ways:

    $stmt = $dbh->prepare($sql);
    // bind to variables that can be a reference
    $stmt->bindParam(":GROUP_ID", $id, PDO::PARAM_INT);
    $stmt->bindParam(":INSTALLED_VERSION_NUM_1", $pt1, PDO::PARAM_INT);
    $stmt->bindParam(":INSTALLED_VERSION_NUM_2", $pt2, PDO::PARAM_INT);
    foreach ($installed_groups as $installed_group){
            $installed_version_parts = explode('.', $installed_group['version']);
            // assign the referenced vars their new value before execute
            $id = $installed_group['group_id'];
            $pt1 = $installed_version_parts[1];
            $pt2 = $installed_version_parts[2];
            $stmt->execute();
    }
    

    Or: (less efficient)

    $stmt = $dbh->prepare($sql);
    foreach ($installed_groups as $installed_group){
            $installed_version_parts = explode('.', $installed_group['version']);
    
            // use bindValue (not bindParam) INSIDE the loop
            // bindValue doesn't set them by reference, so any value expression works
            $stmt->bindValue(":GROUP_ID", $installed_group['group_id'], PDO::PARAM_INT);
            $stmt->bindValue(":INSTALLED_VERSION_NUM_1", $installed_version_parts[1], PDO::PARAM_INT);
            $stmt->bindValue(":INSTALLED_VERSION_NUM_2", $installed_version_parts[2], PDO::PARAM_INT);
            $stmt->execute();
    }
    

    Or:

    $stmt = $dbh->prepare($sql);
    foreach ($installed_groups as $installed_group){
            $installed_version_parts = explode('.', $installed_group['version']);
    
            // pass them on execute directly
            $stmt->execute(array(':GROUP_ID'=>$installed_group['group_id'],
                                 ':INSTALLED_VERSION_NUM_1'=>$installed_version_parts[1],
                                 ':INSTALLED_VERSION_NUM_2'=>$installed_version_parts[2]));
    }