-1

Hello I've tried both these questions solutions (final goal added at bottom)

INSERT array - PDO

Binding values from arrays?

but I don't get the expected variables content in $fields and $newdata

so I kindly print here some var_dump and cast to kindly ask your support.

My array derivate from an html table

For simplicity in my learning experiment I'm working with a dummy table of just 5 fields, as you see they are: selected, user_id, user_name, user_company and user_email. Finally I have inserted just 2 rows of values.

The table content is posted as JSON.stringify.

Here you my results

Using the usual

print_r ( $Arr );

I can see this output

Array ( 
[0] => Array ( [selected] => [user_id] => 3 [user_name] => nome3 [user_company] => azien3 [user_email] => email3 ) 
[1] => Array ( [selected] => 1 [user_id] => 6 [user_name] => nome6 [user_company] => azien6 [user_email] => email6 ) 
)

next I try to apply the code of from the two above questions

24  $fields = implode(",", array_keys($Arr));
25  $newdata = "'" . implode("','", $Arr) . "'";
26  
27  var_dump($fields);
28  echo "<br><br>";
29  var_dump($newdata);

But something is wrong in my interpretation or in my code , because the output is

Notice: Array to string conversion in D:\xampp\htdocs\ajax-json\post.php on line 25

Notice: Array to string conversion in D:\xampp\htdocs\ajax-json\post.php on line 25
string(3) "0,1"

string(15) "'Array','Array'"

can you kindly point out what's wrong? e.g. is my array properly formed?

the final goal is to build a query where they are bind the keys names and key values taken from the associative array directly to columns and values for an INSERT into a mysql table.

In other words since the array's keys names are identical to the database table's columns names, I'm wondering how to make an automatism that creates the query like in the two questions in the opening of this question.

With "automatism" is meant to HAVE variables and maybe cycles to build a query INSTEAD than writing the single columns names and the same for the columns values to be inserted


Edit: from the accepted answer, this is the working code.
$my_keys = array_keys($Arr[0]);

// ---- This prevents PDO SQL Injection
$stmt=$pdo->prepare("DESC my_table");
$stmt->execute();
$whitelist_columns=$stmt->fetchAll(PDO::FETCH_COLUMN);
foreach($my_keys as $key){
    if(!array_search($key,$whitelist_columns)){ echo "ERROR!"; }
}
// ---- End of prevention

$field_names = implode(",", $my_keys);   // build column list

/** @JBH this foreach is needed otherwise the $q_markers will result not PDO placeholders like.
If this is missing, the query inserts all "" values, no matter if you'll adopt bindValue or bindParam**/
foreach($my_keys as &$key){
    $key = ":".$key; 
}
$q_markers = implode(",", $my_keys);     // build PDO value markers

$stmt = $pdo->prepare("INSERT INTO my_table (".$field_names.") VALUES (".$q_markers.")");
foreach($Arr as $key => $val){
    foreach($val as $bind_marker => &$bind_val){   /** @ JBH Without "&" here, it will work 
only bindValue. Instead with "&", they work both bindParam and bindValue **/
        $stmt->bindParam($bind_marker, $bind_val);  
    }
    $stmt->execute();
}
Robert
  • 490
  • 1
  • 5
  • 17
  • What are you expecting in $newdata? You're trying to implode two arrays, which you cannot do. Are you trying to implode each array? $Arr[0] and $Arr[1]? Even that's not right. You can't implode an associative array. Please type out an example of what you expect to see. – JBH Jul 28 '17 at 08:09
  • `$Arr` is a multidimensional array. It behaves not how you expect. – u_mulder Jul 28 '17 at 08:12
  • Mmmh the final goal is to build a query where they are bind the keys names and key values taken from the associative array directly to columns and values for the mysql table. In other words since the keys names are identical to the database table's columns names, I'm wondering how to make an automatism that creates the query like in the two links at top. – Robert Jul 28 '17 at 08:19
  • @JBH thank you for replying. What I want to achieve is what is answered in the two links at top. Read my comment on top of this. With "automatism" is meant to HAVE variables and maybe cycles to build a query INSTEAD than writing the single columns names and the same for the columns values to be inserted – Robert Jul 28 '17 at 08:22

1 Answers1

1

You can implode an associative array, but you cannot implode a multi-dimensional array. That's what the error is telling you. For example...

$my_array = array('a'=>'1', 'b'=>'2', 'c'=>'3');
echo "\n\n".implode(',',array_keys($my_array));
echo "\n\n".implode(',',$my_array)."\n\n";

Results in...

a,b,c

1,2,3

But...

$my_array = array(
    array('a'=>'1', 'b'=>'2', 'c'=>'3'),
    array('d'=>'4', 'e'=>'5', 'f'=>'6')
);
echo "\n\n".implode(',',array_keys($my_array));
echo "\n\n".implode(',',$my_array)."\n\n";

results in...

0,1
PHP Notice:  Array to string conversion in /test.php on line 9

Fixing your code means dealing with the individual data elements. Echo'd out they'd look like this:

selected, user_id, user_name, user_company, user_email
,3,nome3,azien3,email3
1,6,nome6,azien6,email6

So, the basic code would look something like...

$fields = implode(",", array_keys($Arr));
echo $fields."\n";
foreach($Arr as $key=>$val){
    $newdata = "'" . implode("','", $Arr[$key]) . "'";
    echo $newdata."\n";
}

And a PDO INSERT statement would be built like this...

$my_keys = array_keys($Arr[0]);

$stmt=$pdo->prepare("DESC my_table");
$stmt->execute();
$whitelist_columns=$stmt->fetchAll(PDO::FETCH_COLUMN);
foreach($my_keys as $key){
    if(!array_search($key,$whitelist_columns)){ echo "ERROR!"; }
}

$field_names = implode(",", $my_keys);       // build column list
$q_markers = implode(",", $my_keys);     // build PDO value markers

$stmt = $pdo->prepare("INSERT INTO my_table (".$field_names.") VALUES (".$q_markers.")");
foreach($Arr as $key => $val){
    foreach($val as $bind_marker => $bind_val){
        $stmt->bindParam($bind_marker, $bind_val);
    }
    $stmt->execute();
}

Note the section of code with the whitelist variables. The purpose of that code is to protect against SQL injection due to creating the query with unbound column references. PDO does not allow you to bind column names in the same way it does cell data. To protect yourself you must prove that the incoming data matches the columns in the table. If they don't, do something about it (echo "ERROR";). Usually you want to stop that INSERT completely and log the issue somewhere.

$my_keys = array_keys($Arr[0]);
$q_marks = array();

$stmt=$pdo->prepare("DESC my_table");
$stmt->execute();
$whitelist_columns=$stmt->fetchAll(PDO::FETCH_COLUMN);
foreach($my_keys as $key){
    if(!array_search($key,$whitelist_columns)){ echo "ERROR!"; }
    array_push($q_marks, "?");
}

$field_names = implode(",", $my_keys);       // build column list
$field_markers = implode(",", $q_marks);

$stmt = $pdo->prepare("INSERT INTO my_table (".$field_names.") VALUES (".$field_markers.")");

foreach($Arr as $key => $val){
    $stmt->execute($val);
}

The above code is an example of using PDO without bindParam or bindValue. It comes with a price, though usually there's no actual cost. bindParam and bindValue allow you to specifically identify the data type. E.G., bindParam('myval', $myval, PDO::PARAM_INT). When variables are passed as above, you can't do this. Most of the time this is a non-issue as PHP correctly identifies the data type. When PHP does become confused (or if you simply want to impose a check that the data is what you were expecting), then you must use bindParam or bindValue.

JBH
  • 1,823
  • 1
  • 19
  • 30
  • Thank you JBH, I'm impatient to try it out (currently I'm away from home). It seems exactly the right process. This question derives from this other question I've opened but it has been marked as duplicate https://stackoverflow.com/questions/45349503/php-mysql-insert-multimensional-associative-array-building-query-from-array-keys and there I was mentioning PDO as preferred choice! You read my mind ;-) (though maybe PDO is the only choice for this situation (?)) – Robert Jul 28 '17 at 08:49
  • PDO is always the best choice as it protects you against SQL injection. You should always use PDO. – JBH Jul 28 '17 at 09:03
  • That's my goal, I always use PDO, having to learn, I understood it was the one to learn against msqli. I feel comfortable with PDO and it makes things really much readable while if properly implemented, it makes the structure more effective thin lightweight and straightforward e.g. with loops when preparing statements (if needed). E.g. I'm wondering how do they do without PDO placeholders (asked like "how do they do without coffee" :-)).. we know it can be done, but what a mess. Nevertheless PDO prosecutes the object concept which is something to take habit with in perspective. – Robert Jul 28 '17 at 09:23
  • About PDO though here https://phpdelusions.net/pdo/sql_injection_example is explained that one SQL injection is possible either if prepared statements are used ... but... contextually is explained how to avoid that :-) – Robert Jul 28 '17 at 09:35
  • That's a good point. I'd forgotten we're building the column name list dynamically. I've edited my answer to include Whitelist code that protects against this kind of injection. – JBH Jul 28 '17 at 09:42
  • Cool! What a (master)piece of statement :-). Unfortunately I must wait for the test, since I'll be able to try it out not before than this evening (CET) or tomorrow at sunrise. – Robert Jul 28 '17 at 10:49
  • Hello JBH your first "basic code would look something like..." to extract fields names needs a fix, since keys names aren't extracted, only 0,1 . This $fields = implode(",", array_keys($Arr)); shouldn'd be $Arr[0] instead than $Arr? – Robert Jul 30 '17 at 17:06
  • Also both $field_names and $field_markers do refer to implode(",", $my_keys); . It looks like a typo error, could it be? (I'm not yet back to home, but occasionally I'm trying to "translate" your code, please remember I'm a newbie) – Robert Jul 30 '17 at 17:14
  • @Robert, Given a simple array `array('col1', 'col2', 'col3')` I chose to create the PDO bindings using those names. The PDO bindings would usually look something like `col1=:col1` in the query. I had to add that colon in front of each name before processing. Thus, `field_names` are the column names and `field_markers` are the column names preceded with a colon. – JBH Jul 31 '17 at 04:02
  • And to the coment before that, you're correct. Missing the `[0]` was an oversight on my part. – JBH Jul 31 '17 at 04:04
  • hello finally I am here testin the PDO snippet and adding my comments in my php file :-). I have edited `$my_keys = array_keys($Arr);` into `$my_keys = array_keys($Arr[0]);` and run. No errors but there is something that doesn't work as expected. I have checked what has been inserted in my dummy db table. I find the last value inserted in all the 4 columns. Like email6 email6 email6 email6. Echo prepare string `INSERT INTO tabella1 (col01,col02,col03,col04) VALUES (:col01,:col02,:col03,:col04)` looks correct. Echo'd $bind_marker and $bind_val, bind_markers are without the `:` is that fine? – Robert Jul 31 '17 at 06:00
  • Solved, I tried with `bindValue` instead than `bindParam` and it works. Well just a curiosity. I have tried adding the `:` in this manner `bindValue(":".$bind_marker, $bind_val);` and it works the same. Now I have removed the `":".` but, can you explain why it works in both forms? Thank you – Robert Jul 31 '17 at 06:21
  • The colon is only used in the `prepare` statement to seperate the placeholder from the regular SQL query. It is ignored in the `bindValue` and `bindParam` statements (and, indeed, isn't needed). Note that the difference between `bindValue` and `bindParam` is that `bindValue` is for assigning values directly (e.g., `bindValue('obj', 1)`) and `bindParam` is for binding variable names (e.g., `bindParam('obj', $myvar)`). – JBH Jul 31 '17 at 06:49
  • OK, usually that's the difference between bindParam and bindValue... :-) – JBH Jul 31 '17 at 06:50
  • Thank you for all your effort and your kind and patient teach. Ouh well, while I can understand that in this case through the foreach variables this turns in directly bind the value and so the point is bindValue, may I kindly ask you which would it be the application case for the bindParam? – Robert Jul 31 '17 at 10:40
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/150587/discussion-between-robert-and-jbh). – Robert Jul 31 '17 at 10:40
  • I was trying to get rid of bindValue and bindParam, and looking here http://php.net/manual/en/pdo.prepared-statements.php . Theoretically it should work either with bindParam. Could it be the position of the statement $stmt->execute() ? – Robert Jul 31 '17 at 14:32
  • OK, I updated the answer with an example showing how to avoid using `bindParam` and `bindValue`. – JBH Jul 31 '17 at 15:00
  • Seen. But not completely fixed. First line $my_keys is missing the square brackets while as written five message back, with bindParam, the insert goes wrong. In all the db columns gets inserted the same value (the last of the foreach loop). While JUST substituting bindParam with bindValue it works. See the example in that message. – Robert Aug 01 '17 at 06:34
  • Hello JBH, I have tried the new code. You have removed the `foreach` that was adding the colon `:` to the keys for the next `$q_markers` assignment. With this modification it doesn't work nor with bindParam neither with bindValue. – Robert Aug 05 '17 at 06:05
  • I have finally guessed out what is wrong and now I have the working and fixed code. The `foreach` to add the colon `:` for the `$q_markers` is definitely needed (it is disappeared, make it there again please). To make bindParam working, and it will work either with bindValue, it is needed one `&` before `$bind_val` in this `foreach($val as $bind_marker => $bind_val)`, it must become `foreach($val as $bind_marker => &$bind_val)` – Robert Aug 05 '17 at 06:16
  • @Robert, please do me a favor and edit your question. Add at the end your current code so I can see what we're working with. Adding the ampersand (`&`) doesn't make sense. Removing the colons (`:`) in my second example was necessary to execute the PDO by assignment rather than by manual binding. This suggests the code has become a bit mixed up, which is why I need to see what you've done. Thanks! – JBH Aug 05 '17 at 10:47
  • Hello JBH ! I have edited my question as you requested. I've always used the exact same code of your snippet, mean also same variables names. As written in my previous comments I was not getting rid of using bindParam, it was only working with bindValue until I have found one post in one blog and I tried adding that (`&`). About the (`:`), the snipped has worked since the begin WITH the foreach for the (`:`) adding. As soon I removed the loop from your last edit, it stopped working either with bindValue, so I leave that (`:`) loop untouched. – Robert Aug 06 '17 at 05:17