Mr Sorbose
Mr Sorbose

Reputation: 777

fgetcsv / fopen in reverse

I am trying to do this exact function, only want to be able to display the last 20 lines of the document?

    $file = fopen("/tmp/$importedFile.csv","r");
    while ($line =  fgetcsv($file))
    {
        $i++;
        $body_data['csv_preview'][] = $line;
        if ($i > 20) break;
    }
    fclose($file);

I have tried changing the "r" in $file = fopen("/tmp/$importedFile.csv","r");however it seems there is only variations of where to put the pointer with read and write.

I feel this could be an easy one. my apologies.

Upvotes: 1

Views: 2179

Answers (4)

Niels Thiebosch
Niels Thiebosch

Reputation: 147

I came up with this:

$file = fopen("/tmp/$importedFile.csv","r");
$start = count( file( $file ) ) - 20;
$i = 0;
while ($line =  fgetcsv($file)) {
    $i++;
    if ( $i > $start ) $body_data['csv_preview'][] = $line;
}
fclose($file);
//Body_data has now the last 20 lines.

Hope this helps

Upvotes: 1

fvu
fvu

Reputation: 32953

This is one way

$fileend = array();
$file = fopen("/tmp/$importedFile.csv","r");
while ($line =  fgetcsv($file))
{
    // we have a line, so if $fileend already contains the required number
    // of lines we have to make some room.
    if (count($fileend) > 20) {
        $fileend=array_shift($fileend);
    }
    // add freshly read line to array's end
    array_push($fileend,$line);
}
fclose($file);
// at this point $fileend will contain the 20 last lines of the file.

I can't guarantee you that it will be blindingly fast though...

A muich faster way would be to store the lines in a fixed-size circular buffer, which is easier than it sounds

$i=0;
while ($line =  fgetcsv($file))
{
    // store as linenumber modulo 20 'th element in array
    $circularbuffer[$i % 20] = $line;
    $i++;
}

And then to read it

// must start reading after last written element, $i has the correct value.
// and we will read 20 times - same modulo calculation to "circulate" buffer
for ($j=$i;$j<$i+20;$j++) {
    $body_data['csv_preview'][] = $circularbuffer[$j%20];
}

Obviously the big advantage here is that you read the file only once, and I think that the read operation is by far the costliest (in execution time) part of the function.

Upvotes: 0

nickb
nickb

Reputation: 59699

One way to do this is to use an SqlFileObject. First, you need to know how many lines are in the file, which you can calculate like this:

$filename = "/tmp/$importedFile.csv";

// Create a new object for the file
$file = new SplFileObject( $filename, "r");

$lines = 0;
while ( !$file->eof()) {
   $file->fgets();
   $lines++;
}

Now you know there are $lines number of lines in the file. Then, you have to seek to the $lines - 20 line number, and read your CSV data until EOF, like this:

$file->seek( $lines - 20);
while ( !$file->eof()) { 
    $body_data['csv_preview'][] = $file->fgetcsv();
}

Perhaps there is a more efficient way to calculate $lines. Also, you should confirm that there is more than 20 lines in the file before attempting to seek() to $lines - 20.

Upvotes: 2

Ozerich
Ozerich

Reputation: 2000

Your code returns first 20 lines. Try modify it for last 20 lines

if($i > 20)
   array_shift($body_data['csv_preview'])

Upvotes: 1

Related Questions