0

I currently have a form that is built from an uploaded CSV. When the user uploads the CSV and hits 'Preview' Button it directs to a window that shows the entire CSV in a form table that is editable. The CSV is 5 records and 229 fields. The input names are built from row count and column count, so with this CSV it should start at row1col1 and go to row5col229.

I've discovered that the names are working like that, as expected, however I still have a problem. SOme of the CSV files will have 4 rows and some may have 8 or 9. I need to find a way to take the form input and submit it all into the 229 field staging table.

Is there a way to create an array and statement for one row and loop it for however many rows are actually present?

Here's my current code:

if(isset($_POST['preview']))
{
ini_set('auto_detect_line_endings', true);


$file = $_FILES["file"]["tmp_name"];
$handle = fopen($file, "r");
$maxPreviewRows = PHP_INT_MAX;  // this will be ~2 billion on 32-bit system, or ~9 quintillion on 64-bit system
$hasHeaderRow = true;
    echo '<form method="post">';
    echo '<table>';

    if ($hasHeaderRow) {
        $headerRow = fgetcsv($handle);
        echo '<thead><tr>';
        foreach($headerRow as $value) {
            echo "<th>$value</th>";
        }
        echo '</tr></thead>';
    }

    echo '<tbody>';

    $rowCount = 0;
    while ($row = fgetcsv($handle)) {
        $colCount = 0;
        echo '<tr>';
        foreach($row as $value) {
        echo "<td><input name='row".$rowCount."col".$colCount."' type='text' value='$value' /></td>";

            $colCount++;
        }
        echo '</tr>';

        if (++$rowCount > $maxPreviewRows) {
            break;
        }
    }
    echo '</tbody></table>';
    echo '<input type=\'submit\' value=\'Submit\' >';
    var_dump($_POST);
    echo '</form>';
}
?> 

I feel like I'm on the right track but I have no idea how to go about building the array of elements or the statement so that it is a template, so to speak, and loop it for all rows.

Martin
  • 19,815
  • 6
  • 53
  • 104
Geoff_S
  • 3,969
  • 3
  • 19
  • 64

2 Answers2

1

In answer to the comments on the Answer by Tom:

You can set vales in the form into an array $_POST['rows']['columns'] ad then simply count($_POST['rows']); to count values and then foreach over each value in the row.

-- Martin

So I wouldn't need to go through and declare 229 elements? Just create the array and count, then loop with the foreach? In this case, how would I create a statement in SQL to insert to the database?

-- Tom

Your form would be an array of POST values such as

foreach($row as $value) {
        echo "<td><input name='row[".$rowCount."][".$colCount."]' type='text' value='$value' /></td>";

            $colCount++;
        }  

This will then produce an array POST value such as:

$_POST['row'][1][1] = $value;
$_POST['row'][1][2] = $value;
$_POST['row'][1][3] = $value;
...
$_POST['row'][1][229] = ...;
$_POST['row'][2][1] = ... ;
...
$_POST['row'][2][229] = ...;
...
$_POST['row'][5][229] = ...;

You can then run a foreach loop on this array and then extract the value of the data saved, for each key of the array:

$sql = $inserts = $binds = [];
foreach ($_POST['row'] as $rowValue){
    if(is_array($rowValue) && count($rowValue) > 0 ){
        foreach($rowValue as $rowData){
           /***
            * Stupidly, I had missed that row contains arrays 
            * rather than values, so you need a foreach, inside the 
            * foreach as so:
            ***/
            foreach ($rowData as $columnKey  => $columnValue){
                //$columnValue will now equal $value
                //$columnKey will be the column number (1...229)
                /***
                 * This is the area you can construct your SQL query values.
                 * db_connection is assumed to be setup.
                 ***/
                 $sql[] = "`column_name_".$columnKey."`"
                 $binder = "value".$columnKey;
                 $inserts[] = ":".$binder;
                 $binds[$binder] = $columnValue;
                 unset($binder);
            }
           unset($columnKey,$columnValue);             
       }
       unset($rowData);
       /***
        * This is the area the SQL query is set on a per row basis
        ***/
       $sqlFull = "INSERT INTO <table> (".implode(",",$sql).") VALUES(".implode(",",$inserts).")";
       $db_connection->prepare($sqlFull); 
       /***
        * EDIT: bind param MUST come after the prepare call
        ***/
       foreach($binds as $bindKey=>$bindRow){
            $db_connection->bind_param(":".$bindKey, $bindRow);
       }
       unset($bindKey,$bindRow);      
       $sql = $inserts = $binds = []; //reset arrays for next row iteration. 
       /***
        * db_connection then executes the statement constructed above
        ***/
        $db_connection->execute();
     } //close if.
}
unset($rowValue);

Please note this a quick and dirty example only and I've not had time to check my syntax is exact, but it's more to give you a rough idea of the query structure

You can use count() to count the rows and columns in the $_POST array.

Community
  • 1
  • 1
Martin
  • 19,815
  • 6
  • 53
  • 104
  • Thank you! I will try this now. Quick and dirty is all I need, they just want to see this as an option, so I could refine at a later date. – Geoff_S May 04 '17 at 16:01
  • 1
    I'm afraid this will need a bit of editing as you can't `->bindparam` *before* setting the `->prepare` – Martin May 04 '17 at 16:08
  • This is where my complete lack of experience in PHP shows, and I know there's risk of SQL injection but this is all a test project. I don't have any prepared statements, so I'm not using that method anywhere. And my database connection is setup as $connect, so I just changed the name – Geoff_S May 04 '17 at 16:14
  • 1
    @TomN. I have updated my answer so the bind params should work correctly now. I must highly encourage you to look into prepared statements as well as making full use of [PHP error logging](http://stackoverflow.com/questions/1053424/how-do-i-get-php-errors-to-display). It wil help find issues so much quicker than blank screens! Good luck! – Martin May 04 '17 at 16:18
  • I understand, completely! Thank you so much for the help and the advice – Geoff_S May 04 '17 at 16:25
  • sorry, I have a quick question. I edited everything to get it working with my database and syntax. It even printed my query message but there was a message that the query was empty – Geoff_S May 04 '17 at 16:57
  • Can you perhaps edit your answer (code doesn't look good in comments) and show what your query (`->prepare($sqlFull);`) looks like? also see if `$binds` is populated with `var_dump($binds)` too. @TomN. – Martin May 04 '17 at 17:03
  • ok i edited with the full code. the var_dump is actually not showing up on the page for some reason – Geoff_S May 04 '17 at 17:17
  • @TomN. your `var_dump` is in the wrong place, try putting the var_dump just before the `$binds =[];` line. Also possibly useful to `print "
    ";` right after it too as it will run every row
    – Martin May 04 '17 at 17:23
  • I edited the code below and took away the isset submit option, so basically, once the page is called it executes that code. IN the code below, my page is printing the row array from the post, but nothing below that. – Geoff_S May 04 '17 at 20:45
  • @TomN. ok, a few things: 1) var_dump may not always output to screen you may need to `view-source` of your HTML to view that data. 2) you don't need `mysqli_mutli_query` because you're only executing one query. 3) when you `var_dump($_POST)` does it show row as an array? If nothing else is output it looks like the row is failing the `if` statement . Let me know how this goes `:)` – Martin May 05 '17 at 10:41
  • Ok, thank you for responding again. I made it mysqli_query, first of all. Also, It's really acting strange with the isset command. If I remove the isset $_POST loop and it printed my var_dump($_POST), but if the isset loop is around everything it only loads the page and does nothing inside that loop. But even with it removed, it won't execute the SQL, however the var_dump($_POST ) works and prints this: array(1) { ["row"]=> array(5) { [0]=> array(229) { [0]=> string(1) "0" [1]=> string(14 – Geoff_S May 05 '17 at 15:25
  • 1
    @TomN. I don't like using `isset`, I think it's far too vague a function. – Martin May 05 '17 at 15:27
  • 1
    I spotted the issue, my `foreach` loop in my answer doesn't take into account that `row` *contains* arrays (of columns). I will edit it.... – Martin May 05 '17 at 15:29
  • @TomN. this should work better now, but is still a *quick and dirty* solution! – Martin May 05 '17 at 15:34
  • Thank you, I updated that on my server and now when I var_dump $binds I get: array(0) { } I added another debug statement that doesn't print but it's still not submitting to the database. I wonder if there's something in that code that isnt matched up with the form code – Geoff_S May 05 '17 at 15:42
  • And thank you again for your help, sorry for the back and forth, I'm just trying to keep it updated – Geoff_S May 05 '17 at 15:43
  • 1
    @TomN. the way I would suggest would be to put `print_r()` in your code for every variable (with a describer for each one such as `print "binder: ".print_r($binder,true)."
    ";` ) and work through it like that, to see exactly what data is being placed where, and then using that to tweak the code so the data is the correct shape for the SQL to use. From this you should also be able to more practically see what is actually going on and where. Good luck.
    – Martin May 05 '17 at 16:18
0

I actually figured this out, and The names are working as expected. However, I have an issue. Some CSV files will have 5 rows and some will have more, so I can't create a static way to do this by just input names. Is there a way to create one array and statement and loop it for however many rows are present?


EDIT The current source code used for solving issues raised in comments to Martins answer.

<?
$connect = mysqli_connect($server, $user, $pw, $db);

if ($connect->connect_error) {
die("Connection failed: " . $conn->connect_error);
}else{
echo'success!';
}

 var_dump($_POST);


     $sql = $inserts = $binds = [];
           foreach ($_POST['row'] as $rowValue){
         if(is_array($rowValue) && count($rowValue) > 0 ){
                foreach($rowValue as $columnKey  => $columnValue){
       //$columnValue will now equal $value
       //$columnKey will be the column number (1...229)
       /***
        * This is the area you can construct your SQL query values.
        * db_connection is assumed to be setup.
        ***/
        $sql[] = "`column_name_".$columnKey."`";
        $binder = "value".$columnKey;
        $inserts[] = ":".$binder;  
        $binds[$binder] = $columnValue;
        unset($binder);
    }
   unset($columnKey,$columnValue);
   /***
    * This is the area the SQL query is set on a per row basis
    ***/
   $sqlFull = "INSERT INTO staging (".implode(",",$sql).") VALUES(".implode(",",$inserts).")";
   $connect->prepare($sqlFull); 
   /***
    * EDIT: bind param MUST come after the prepare call
    ***/
   foreach($binds as $bindKey=>$bindRow){
        $connect->bind_param(":".$bindKey, $bindRow);
   }

   unset($bindKey,$bindRow); 
   var_dump($binds);   
   $sql = $inserts = $binds = []; //reset arrays for next row iteration. 
   /***
    * db_connection is then given the SQL. 
    ***/
    $connect->execute();

  echo "<p>\$sqlFull:<pre>".print_r($sqlFull,true)."</pre></p>\n";

  if(mysqli_multi_query($connect, $sqlFull)) 
  {
    echo'File submitted'; 
  } else { 
    echo "Error: " . mysqli_error($connect); 
  }
 } //close if.


}

unset($rowValue);



?>
Geoff_S
  • 3,969
  • 3
  • 19
  • 64
  • you can set vales in the form into an array `$_POST['rows']['columns']` ad then simply `count($_POST['rows']);` to count values and then `foreach` over each value in the row. – Martin May 04 '17 at 15:18
  • So I wouldn't need to go through and declare 229 elements? Just create the array and count, then loop with the foreach? In this case, how would I create a statement in SQL to insert to the database? – Geoff_S May 04 '17 at 15:19
  • That's a pretty big question; you can use the foreach loop to construct the parameters of the SQL insert and then after the loop run the insert. There will be lots of reference material to this on SO – Martin May 04 '17 at 15:39
  • Ok, I think I understand. I will do some research on that, thank you – Geoff_S May 04 '17 at 15:41