如何使用数组为UPDATE正确创建PDO准备的SQL语句

i am lost trying to figure out how to properly set up a prepared PDO SQL statement when updating an existing record.

my array being passed into my method is such:

array(9) { ["catName"]=> string(10) "Kato Marks" ["age"]=> string(2) "40" ["gender"]=> string(6) "Female" ["createTime"]=> string(19) "2017-10-03 03:55:39" ["coloring"]=> string(5) "Black" ["hairLength"]=> string(5) "Short" ["currentMood"]=> string(5) "rowdy" ["weight"]=> string(2) "17" ["hasCatittude"]=> string(1) "0" }

with key is the database table column, the value is the database value i wish to update

so far i have the following:

public function updateRow($data, $catID)
{
    $db = self::getConnection();

    try
    {
        $stmt = $db->prepare("UPDATE users SET " . implode('= ', array_keys($data).  array_values($data)). " WHERE id='{$catID}'");

        foreach($data as $key => $value) {
            $stmt->bindValue($key, $value, PDO::PARAM_STR);
        }
        $stmt->execute();
    } catch (\PDOException $e) {
        return 'Adding new record failed' . $e->getMessage();
    }

}

but when i print_r the statement to the screen i get the following:

PDOStatement Object ( [queryString] => UPDATE users SET WHERE id='108' )

i am unable to figure out how to properly write the prepared statement, where i dynamically set the table name to the array key, etc. any help would be greatly appreciated.

You need to update your function to something like this. You first will build your column array, implode it with ', '. Loop though and bind your variables, then bind your $catID.

public function updateRow($data, $catID)
{
    $db = self::getConnection();

    try
    {
        foreach($data as $key => $value) {
            $columns[] = '`' . $key . '` = :' . $key;
        }

        $stmt = $db->prepare("UPDATE users SET " . implode(', ', $columns). " WHERE id=:catID");

        foreach($data as $key => $value) {
            $stmt->bindValue($key, $value, PDO::PARAM_STR);
        }

        $stmt->bindValue('catID', $catID, PDO::PARAM_INT);

        $stmt->execute();
    } catch (\PDOException $e) {
        return 'Adding new record failed' . $e->getMessage();
    }

}

Re-evaluate the approach

From your comment, this function belongs in a Database class. Admittedly, this class could be used on any table to update any row based on any criteria. However, looking at the function signature, we can see it is supposed to accept a $catId parameter, implying it will be used on a very specific table, filtering rows using a specific column.

You could get around this by simply renaming the function and the parameters to more closely represent what it is actually doing but that still wouldn't solve what is, in my opinion, the core of the problem: The object has too many responsibilities.

According to the Single Responsibility Principle, a class "should have one, and only one, reason to be changed". In this case, if a column is added to the User table, or that another column needs to be added to the WHERE clause, only the User class (or Entity) should be affected while the Database class should not need any changes.

Suggestions

For the sake of simplicity, let's imagine a fridge database, with a table called fruits (I know, very imaginative).

create table fruits (
    id int not null auto_increment,
    name varchar(20),
    color varchar(20),
    weight int,
    order int, /* order of appearance, say, for a list */
    PRIMARY KEY (id)
);

The php representation of this table would probably look a bit like this

class Fruit {
    private $id;
    private $name;
    private $color;
    private $weight;
    private $order;

    public function __construct($id, $name, $color, $weight) {
        $this->id = $id;
        $this->name = $name;
        $this->color = $color;
        $this->weight = $weight;
        $this->order = $order;
    }
}

Great, now we have a table, and an object that can be used to represent a record of that table. Now we can add a FruitTable class that will act as a medium between Fruit and the Database itself.

class FruitTable {
    private $columns = ['id', 'name', 'color', 'weight', 'order'];
    private $table = 'fridge.fruits';
    private $conn;

    public function __construct($connection) {
        $this->conn = $connection
    }

    public function findOneById($id) {
        /**
         * Query the DB and hydrate a Fruit instance
         */

        return $fruit;
    }
}

Now imagine we need to be able to change the order field of Fruit based on the color of the fruit (I know, this is some really weird business we're working for). No problem, we can add a function to FruitTable to make the update.

public function updateOrderByColor($newOrder, $color) {
    /* assume proper validation */
    $query = $this->conn->prepare("UPDATE fruits SET order = :newOrder WHERE color = :color");
    $query->bindParam(':newOrder', $newOrder);
    $query->bindParam(':color', $color);

    $query->execute();
}

This is a very simplistic example, but it shows how much flexibility can be gained over having a simple Database class. If a column needs to be added, or a condition changed, there is only one place in the code you need to jump in and mess around with. If there's a problem with a query, there's only one place to look for that bug.

This example isn't perfect, FruitTable probably shouldn't have the responsibility of dealing with the database connection directly. You can look at how Doctrine and Eloquent approach query builders, and how they manage to keep each class encapsulated from the rest.