Search code examples
phpfilehandlefile-processingfgetc

PHP get the last line of an open file handle with multiple lines


This seems like it should be a simple thing to do but I'm having a bit of trouble with fgetc() when returning the last line of a open file handle. what I'm trying to do is return the last line written to the handle, I have the following which works if the handle has only one line:

function getLastLineFromHandle($handle)
{

    $seeker = function($handle) use (&$seeker) {

        fseek($handle, -1, SEEK_CUR);

        return ftell($handle) ?
            $seeker($handle) :
            $handle;
    };

    return trim(fgets($seeker($handle)));
}

$handle = fopen('php://temp', 'w+');
fwrite($handle, 'Hello World'.PHP_EOL);

//prints Hello World
print getLastLineFromHandle($handle);

The problem is when I have multiple lines written to the handle, adding an fgetc() to the check condition doesn't seems to work for example:

function getLastLineFromHandle($handle)
{

    $seeker = function($handle) use (&$seeker) {

        fseek($handle, -1, SEEK_CUR);

        return ftell($handle) && fgetc($handle) != PHP_EOL ?
            $seeker($handle) :
            $handle;
    };

    return trim(fgets($seeker($handle)));
}

This returns blank if multiple lines are written to the handle and fgetc($handle) seems to return the same character each time?

I'm sure there is something very simple that I've missed, but any pointers would be great as this is driving me crazy!

Thanks.


Solution

  • Found what was missing from example above, turns out it was case of there being an unexpected end of line char at the pointer at start so moving one position in solves the issue, for example:

    function getLastLineFromHandle($handle)
    {
        $seeker = function($handle) use (&$seeker) {
            fseek($handle, -2, SEEK_CUR);
    
            return ftell($handle) && fgetc($handle) != PHP_EOL ?
                $seeker($handle) :
                $handle;
        };
    
        return trim(fgets($seeker($handle)));
    }
    

    While exploring this I also found another way of doing the same thing, thanks to @TheMarlboroMan's comment about seeking to the end which sparked this:

    function getLastLineFromHandle($handle)
    {
        $seeker = function($handle, $cur = -2, $line = '') use (&$seeker)
        {
            $char = '';
            if (fseek($handle, $cur, SEEK_END) != -1) {
                $char = fgetc($handle);
                $line = $char.$line;
            }
    
            return ftell($handle) > 0 && $char != PHP_EOL?
                $seeker($handle, $cur-1,$line) :
                $line;
        };
    
        return trim($seeker($handle));
    }
    

    This hasn't been through a refactor loop, however it passes the same tests as the other method above. This solution seems to be a bit messy to return the line string instead of the file handle as that is what you would expect back.

    Setting this as solved but open for comments if anyone has a different way :)