1

I am using PDO to insert into a MySQL DB input from users on a form with 10 input fields. I make a numeric array containing the $_POST values by doing this:

foreach ($_POST as $key => $val)
{
     //testInput() sanitizes the input
     $postArray[] = testInput($val);
}

From all the examples and answers I have browsed through online and on SO using PDO to insert data, they do something like this(https://stackoverflow.com/a/767520/3040381):

$db = new PDO("...");
$statement = $db->prepare("insert into some_other_table (some_id) values (:some_id)");
$statement->execute(array(':some_id' => $row['id']));

The problem I'm having is, I have almost 10 user inputs to insert into my DB. Using the method above, I'd have to do something like this:

$statement->execute(array(':id1' => $row['id'], ':id2' => $row['id2'], ':id3' => $row['id3'], ........':id10' => $row['id10'], ':id11' => $row['id11']));

That's a lot of typing, and makes me wonder if I'm doing it correctly, but I'm not seeing any other way of doing mass INSERTs with PDOs online.

I simplified it a bit like this:

    //ConnectDB() is a method in another file that returns a PDO object
$dbHandle = ConnectDB();

/**
* Gets the columns of a MySQL table
* @param {String} $tableName is the name of the table
* @param {Object} $handle is the PDO object
* @return {array} returns a numeric array containing column names
*/
function getColumnNames($tableName, $handle) {
    $query = $handle->prepare("SHOW COLUMNS FROM $tableName");
    $query->execute();
    $table_fields = $query->fetchAll(PDO::FETCH_COLUMN);
    return $table_fields;
}

/**
* Builds an INSERT query using named placeholders
* @param {String} $tableName is the name of the table to be inserted
* @param {Object} $handle is the PDO object
* @return {String} returns a INSERT query
*/
function getColumnStrings($tableName, $handle) {

    $columnArray = getColumnNames($tableName, $handle);
    $size = sizeof($columnArray);
    $sql = "(";
    for($i = 0; $i < $size; $i++) {
        $sql .= $columnArray[$i] . ",";

    }
    $sql = rtrim($sql, ",");
    $sql .= ")";
    $values = "VALUES (";
    for($i = 0; $i < $size; $i++) {
        $values .= ":" . $columnArray[$i] . ",";

    }
    $values = rtrim($values, ",");
    $values .= ")";


    return "INSERT INTO " . $tableName . " ". $sql . " " . $values;

}

/**
* Builds an associative array to place inside query->execute()
* @param {array} $postArray numeric array containing $_POST values
* @param {Object} $handle is the PDO object
* @param {String} $tableName is the name of the table
* @return {array} returns an associative array with 
*                 values to be replaced
*/
function buildArr($postArray, $handle, $tableName) {
    $namesArray = getColumnNames($tableName, $handle);
    $size = sizeof($namesArray);
    $paramArray = [];
    for($i = 0; $i < $size; $i++) {
        $namesArray[$i] = ":" . $namesArray[$i];

    }

    for($i = 0; $i < $size; $i++) {
        $paramArray[$namesArray[$i]] = $postArray[$i];
    }

    return $paramArray;
}

I place the named placeholders in a numeric array, and then loop through the placeholder array simultaneously with the array containing $_POST values, and create a new associative array to place inside the $query->execute() argument.

I can also, instead of making a new array, do bindValue() and bind each index element from the two arrays as well, which I think would be an alternative. Is my approach to inserting huge amounts of user inputs the correct way?

Community
  • 1
  • 1
Honinbo Shusaku
  • 1,411
  • 2
  • 27
  • 45
  • With that many inputs (why o why would you put that in a single table btw!) you should just use unnamed parameters (`?`). That way you can pass an array of values alone and it's the order of the values that counts. – Blizz Jun 11 '15 at 13:58
  • That seems like way too many fields for one table; however, if you want to keep it this way, you could always do a loop with bindParam(). – versalle88 Jun 11 '15 at 14:00
  • @versalle88 is that similar to how I suggested "do bindValue() and bind each index element from the two arrays as well" or can you provide an example please – Honinbo Shusaku Jun 11 '15 at 14:01
  • @Abdul Yes, it would be similar. You call bindValue() and bindParam() in a similar fashion, but they are handled differently by PHP. – versalle88 Jun 11 '15 at 14:04
  • @versalle88 Okay, thank you, got it. But what's wrong with creating an associative array associating the named parameter and the value, as isn't that essentially the same thing, except for an extra loop? – Honinbo Shusaku Jun 11 '15 at 14:06
  • I, myself, can't imagine any reason to store that number of records in a single row. Regardless, relying on positions in arrays that were generated from input when storing into a database rarely ends up working out well in the long run. "Oops, Billy added a new field in the middle, now everything is broken.. again." – Jacob S Jun 11 '15 at 14:07
  • @JacobS Let's say there were 10 user inputs. How would you use PDO to dynamically insert into the database? – Honinbo Shusaku Jun 11 '15 at 14:10
  • 1
    For me -- I would have an array of html form input names to database column names. Then I could loop through the inputs and generate the query in a loop, keeping a separation from the input names/order/etc, and the database column names/order/etc. You are partially doing this, but it appears dynamically, which in my opinion is an unnecessary overhead and binds your interface to the database. – Jacob S Jun 11 '15 at 14:16
  • Think of it this way -- your script should be the go-between. A database should be able to change and your interface should be able to change, completely independent of each other. If they can't, you're trapped and should expect things to break. It creates an unclear and often undocumented dependency that can only cause issues for yourself and other/future developers who attempt to maintain or improve your application, without providing any benefit what-so-ever. – Jacob S Jun 11 '15 at 14:22
  • @JacobS I see what you're saying. Right now, my DB columns are ordered to be exactly ordered as my HTML input order. – Honinbo Shusaku Jun 11 '15 at 14:27

1 Answers1

1

Your example is a way to do it, but I will give my example of how I would handle this, especially when you can potentially have a significant number of inputs, without constantly needing to update SQL queries or the database when you were to add, remove, or reorder the input, or without needing to create an unnecessary dependency between the interface and database.

Let's assume we've already performed any checks we want to make, that we know whatever values are in the array are to be inserted, I would do something along the lines of:

<?php
//This array gives us a way to match our incoming data to where it needs to be stored.
$lookup = array( 'form1' => 'id1', [...]);

$columns_params = array();
$params_values = array();

foreach($lookup as $form_key => $column_key) {
    $param_key = ':'.$column_key;
    $columns_params[$column_key] = $param_key;
    $params_values[$param_key] = $_POST[$form_key];
}

$insert_statement = sprintf('insert into some_other_table (%s) VALUES (%s)', implode(',',array_keys($columns_params)), implode(',',array_values($columns_params)));

$statement = $db->prepare($insert_statement);
array_map(function($param, $value) use (&$statement) {
                $statemnt->bindParam($param, $value);
            }, $params_values);
$statement->execute();
Jacob S
  • 1,693
  • 1
  • 11
  • 12
  • Yes, I was thinking around the same thing after your comment. I was going to rename my HTML inputs name to the database column names. And then, in the PHP side, in a loop, I'd bind $_POST[columnName] (since the column names and HTML names are the same, and I already have a function to retrieve column names to an array) to the named parameter. This unties me of worrying about the order, while also allowing me to build the query in a loop. – Honinbo Shusaku Jun 11 '15 at 15:09