filegetcsv causing infinite loop - php

Trying to use filegetcsv to parse a CSV file and do stuff with it, using the following code found all over the Internet, including the PHP function definition page:
if (($handle = fopen("test.csv", "r")) !== FALSE) {
while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
print_r($data);
}
fclose($handle);
}
But the code gives me an infinite loop of warnings on the $data = line:
PHP Warning: fgetcsv() expects parameter 1 to be resource, boolean given in...
I know the file I'm opening is a valid file, because if I add a dummy character to the file name I get a different error and no loop.
The file is in a folder with full permissions.
I'm not using a CSV generated by an Excel on Mac (there's a quirky error there)
PHP version 5.1.6, so there should be no problem with the function
I know the file's not too big, or malformed, because I kept shrinking the original file to see if that was a problem and finally just created a custom file in Notepad with nothing more than two lines like:
Value1A,Value1B,Value1C,Value1D
Still looping and giving no data. Here's the full code I'm working with now (using a variable that's greater than the number of lines so I can prove that it would loop infinitely without actually giving my server an infinite loop)
if ($handle = fopen($_SERVER['DOCUMENT_ROOT'].'/tmp/test-csv-file.csv', 'r') !== FALSE) {
while ((($data = fgetcsv($handle, 1000, ',')) !== FALSE) && ($row < 10)) {
print_r($data);
$row++;
}
fclose($handle);
}
So I really have two questions.
1) What could I possibly be overlooking that is causing this loop? I'm half-convinced it's something really "face-palm" simple...
2) Why is the recommended code for this function something that can cause an infinite loop if the file exists but there is some unknown problem? I would have thought the purpose of the !== FALSE and so forth would be to prevent that kind of stuff.

There's no question about what's going on here: the file is not opened successfully. That's why $handle is a bool instead of a resource (var_dump($handle) to confirm this yourself).
fgetcsv then returns null (not false!) because there's an error, and your test doesn't pick this up because you are testing with !== false. As the documentation states:
fgetcsv() returns NULL if an invalid handle is supplied or FALSE on
other errors, including end of file.
I agree that returning null and false for different error conditions is not ideal, and furthermore that it's against the precedent established by lots of other functions, but that's just how it is (and things could be worse). As things stand, you can simply change the test to
while ($data = fgetcsv($handle, 1000, ","))
and it will work correctly in both cases.
Update:
You are the victim of assignment inside an if condition:
if ($handle = fopen($_SERVER['DOCUMENT_ROOT'].'/tmp/test-csv-file.csv', 'r') !== FALSE)
should have been
// wrap the assignment to $handle inside parens!
if (($handle = fopen($_SERVER['DOCUMENT_ROOT'].'/tmp/test-csv-file.csv', 'r')) !== FALSE)
I 'm sure you understand what went wrong here. This is the reason why I choose to never, ever, make assignments inside conditionals. I don't care that it's possible. I don't care that it's shorter. I don't even care that sometimes it's quite less "elegant" to write the loop if the assignment is taken out. If you value your sanity, consider doing the same.

$row = 1;
if (($handle = fopen($_FILES['csv-file']['tmp_name'], "r")) !== FALSE) {
$data = fgetcsv($handle , 1000 , ",");
while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
$num = count($data);
echo "<p> $num fields in line $row: <br /></p>\n";
$row++;
for ($c=0; $c < $num; $c++) {
echo $data[$c] . "<br />\n";
}
}
fclose($handle);
}
Try given Code Snippet once,because as i have noticed you are missing some important things in your code.

Related

Fgets progress - easier way?

I read a big text file ~500MB and want to get the progress during my read operations.
To do so I now count the lines the files has and then compare it to the ones I already read. This needs two complete iterations over the file. Is there an easier way using the filesize and fgets buffer size?
My current code looks like:
$lineTotal = 0;
while ((fgets($handle)) !== false) {
$lineTotal++;
}
rewind($handle);
$linesDone = 0;
while (($line = fgets($handle)) !== false) {
progressBar($linesDone += 1, $lineTotal);
}
Based on bytes rather than lines, but you can quickly get the total size of the file upfront with filesize:
$bytesTotal = filesize("input.txt")
Then, after you've opened the file, you can read each line and then get your current position within the file, something like:
progressBar(0, $bytesTotal);
while (($line = fgets($handle)) !== false) {
doSomethingWith($line, 'presumably');
progressBar(ftell($handle), $bytesTotal);
}
There are caveats about the fact that PHP integers may not handle files over 2G but, since you specified your files are about 500M, that shouldn't be an immediate problem.

Using array_combine to store data read from csv

From a csv file I need to extract the header and the values. Both are later accessed in frontend.
$header = array();
$contacts = array();
if ($request->isMethod('POST')) {
if (($handle = fopen($_FILES['file']['tmp_name'], "r")) !== FALSE) {
$header = fgetcsv($handle, 1000, ",");
while (($values = fgetcsv($handle, 1000, ",")) !== FALSE) {
// array_combine
// Creates an array by using one array for keys
// and another for its values
$contacts[] = array_combine($header, $values);
}
fclose($handle);
}
}
It works with csv files that look like this
Name,Firstname,Organisation,
Bar,Foo,SO,
I just exported my gmail contacts and tried to read them using the above code but I get following error
Warning: array_combine() [function.array-combine]: Both
parameters should have an equal number of elements
The gmail csv looks like this
Name,Firstname,Organisation
Bar,Foo,SO
Is the last missing , the reason for the error? What is wrong and how to fix it?
I found this on SO
function array_combine2($arr1, $arr2) {
$count = min(count($arr1), count($arr2));
return array_combine(array_slice($arr1, 0, $count),
array_slice($arr2, 0, $count));
}
This works but it skips the Name field and not all fields are combined. Is this because the gmail csv is not realy valid? Any suggestions?
I managed this by expanding the array size or slicing it depending on the size of the header.
if (count($header) > count($values)) {
$contacts = array_pad($values, count($header), null);
} else if (count($header) < count($values)) {
$contacts = array_slice($values, 0, count($header));
} else {
$contacts = $values;
}
Although this isn't the answer to the question you asked, it might be the answer to the source of the problem. I recently had this problem and realized I was making a silly error because I didn't understand the fgetcsv() function's parameters:
That 1000 up there denotes the maximum line length of a single line in the csv you're taking content from. Longer than that, and the function returns null! I don't know why the version given in the examples is so stingy, but it's not required; setting it to 0 allows fgetcsv() to read lines of any length. (The documentation warns this is slower. For most use cases of fgetcsv() I can hardly imagine it's slow enough to notice.)

Using fseek to start reading a CSV after a certain number of lines

I am using the current code to read a csv file and add it to an array:
echo "starting CSV import<br>";
$current_row = 1;
$handle = fopen($csv, "r");
while ( ($data = fgetcsv($handle, 10000, ",") ) !== FALSE )
{
$number_of_fields = count($data);
if ($current_row == 1) {
//Header line
for ($c=0; $c < $number_of_fields; $c++)
{
$header_array[$c] = $data[$c];
}
} else {
//Data line
for ($c=0; $c < $number_of_fields; $c++)
{
$data_array[$header_array[$c]] = $data[$c];
}
array_push($products, $data_array);
}
$current_row++;
}
fclose($handle);
echo "finished CSV import <br>";
However when using a very large CSV this times out on the server, or has a memory limit error.
I'd like a way to do it in stages, so after the first say 100 lines it will refresh the page, starting at line 101.
I will probably be doing this with a meta refresh and a URL parameter.
I just need to know how to adapt that code above to start at the line I tell it to.
I have looked into fseek() but I'm not sure how to implement this here.
Can you please help?
The timout can be circumvented using
ignore_user_abort(true);
set_time_limit(0);
When experiencing problems with the memory limit, it may be wise to take a step back and look at what you're actually doing with the data you're processing. Are you pushing the data into a database? calculate something off the data but don't need to store the actual data, …
Do you really need to push (array_push($products, $data_array);) the rows into an array (for later processing)? can you instead write to the database directly? or calculate directly? or build an html <table> directly? or whatever the hell you're doing right then an there, within the while() loop, without pushing everything into an array first?
If you're able to chunk the processing, I guess you don't need that array at all. Otherwise you'd have to restore the array for every chunk - not solving the memory issue one bit.
If you can manage to change your processing algorithm to waste less memory / time, you should seriously consider that over any chunked processing requiring a round-trip to the browser (for so many performance and security reasons…).
Anyways, you can, at any time, identify the current stream offset with ftell() and re-set to that position using fseek(). You'd only need to pass that integer to your next iteration.
Also there is no need for your inner for() loops. This should produce the same results:
<?php
$products = array();
$cols = null;
$first = true;
$handle = fopen($csv, "r");
while (($data = fgetcsv($handle, 10000, ",")) !== false) {
if ($first) {
$cols = $data;
$first = false;
} else {
$products[] = array_combine($cols, $data);
}
}
fclose($handle);
echo "finished CSV import <br>";

Output contacts from csv file with PHP

I need to be able to output contacts via a loop on a page from a CSV file downloaded from Outlook.
If the user has the file on their local machine, I suppose I need some sort of upload mechanism, then let my script read uploaded file and then run the results via some loop and output one contact per line.
Each line will have a checkbox next to a contact and if checked, the form will post results and they will be written into db.
Normal format of Outlook .CSV example file here
I only need Name and email. First and last can be merged in just Name. I suppose i need to run some sort of email validation to reject malformed entries...
Just trying to understand what needs to be done.
You should look into fgetcsv, which can read your CSV file and return an array to you. This is really easy to work with.
$row = 1;
if (($handle = fopen("test.csv", "r")) !== FALSE) {
while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
$num = count($data);
$row++;
for ($c=0; $c < $num; $c++) {
echo $data[$c] . "<br />";
}
}
fclose($handle);
}
For information about reading the csv file check out this http://php.net/manual/en/function.fgetcsv.php

Reading a csv file into an array

I'm incredibly new to php so please bear with me and help me learn. I have a .csv file that's 33 lines long (including titles) and 4 columns wide. I want to read that data into an array so I can begin to sort and manipulate it.
What is the best course of action for doing so? Code snippits are the best way for me to learn code as I can read, interpret, use and then spit back questions I may have.
Using fgetcsv returns an array from a csv file line. To see it exploded you run put your returned array into a print_r() function. To see that in a pretty-print type view you can wrap it in <pre> tags
<?php
if (($handle = fopen("test.csv", "r")) !== FALSE) {
while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
echo "<pre>".print_r($data)." <br /></pre>";
}
fclose($handle);
}
?>
That should be a good start.
http://php.net/manual/en/function.fgetcsv.php

Categories