views:

146

answers:

4

I'm on a server where I'm limited to PHP 5.2.6 which means str_getcsv is not available to me. I'm using, instead fgetcsv which requires "A valid file pointer to a file successfully opened by fopen(), popen(), or fsockopen()." to operate on.

My question is this: is there a way to access a string as a file handle?

My other option is to write the string out to a text file and then access it via fopen() and then use fgetcsv, but I'm hoping there's a way to do this directly, like in perl.

A: 

Unfortunately, that is not possible. You cannot treat a string as if it's a stream from a file. You would indeed have to first write the string to a file, and then open said file using fopen.

And now for the obvious part, have you considered upgrading?

Aistina
Client's server, not my server. Shared hosting environment, yadda yadda yadda ... I'm running the latest version on MY box, ofc.
Erik
Not even with streams? http://www.php.net/streams
Franz
:) Didn't know about those, thanks for the link! I suppose you learn something new every day. Obviously though upgrading would still be the best solution.
Aistina
+8  A: 

If you take a look in the user notes on the manual page for str_getcsv, you'll find this note from daniel, which proposes this function (quoting) :

<?php
if (!function_exists('str_getcsv')) {
    function str_getcsv($input, $delimiter = ",", $enclosure = '"', $escape = "\\") {
        $fiveMBs = 5 * 1024 * 1024;
        $fp = fopen("php://temp/maxmemory:$fiveMBs", 'r+');
        fputs($fp, $input);
        rewind($fp);

        $data = fgetcsv($fp, 1000, $delimiter, $enclosure); //  $escape only got added in 5.3.0

        fclose($fp);
        return $data;
    }
}
?>

It seems to be doing exactly what you asked for : it uses a stream, which points to a temporary filehandle in memory, to use fgetcsv on it.


See PHP input/output streams for the documentation about, amongst others, the php://temp stream wrapper.


Of course, you should test that it works OK for you -- but, at least, this should give you an idea of how to achieve this ;-)

Pascal MARTIN
I'd upvote this twice if could - you always seem to have spot on answers. I need to read the user notes more often.
Erik
Thanks :-) ;;; The users notes quite often bring some interesting piece of information :-) *(Well, if one of use has a problem, chances are someone else already had that same problem before ^^ )*
Pascal MARTIN
Its interesting to note, after doing some reading, you can just use `php://memory` - the only advantage to `php://temp` is after the file exceeds the designated size, it will write to disc; if you're under that file size it stays entirely in memory.
Erik
@Erik : I have to admit I've never been in a situation where I hit that size limit -- but thanks for the note : it's definitly good to know !
Pascal MARTIN
A: 

You can use stream handles such as php://memory to achieve what you're after. Just open, fwrite, rewind, and you should be able to use fgetcsv.

Eric Butera
+2  A: 

To answer your general question, yes you can treat a variable as a file stream.

http://www.php.net/manual/en/function.stream-context-create.php

The following is a copy and paste from a few different comments on the PHP manual (so I cannot vouch for how production ready it is):

<?php
class VariableStream {
    private $position;
    private $varname;
    public function stream_open($path, $mode, $options, &$opened_path) {
        $url = parse_url($path);
        $this->varname = $url["host"];
        $this->position = 0;
        return true;
    }
    public function stream_read($count) {
        $p=&$this->position;
        $ret = substr($GLOBALS[$this->varname], $p, $count);
        $p += strlen($ret);
        return $ret;
    }
    public function stream_write($data){
        $v=&$GLOBALS[$this->varname];
        $l=strlen($data);
        $p=&$this->position;
        $v = substr($v, 0, $p) . $data . substr($v, $p += $l);
        return $l;
    }
    public function stream_tell() {
        return $this->position;
    }
    public function stream_eof() {
        return $this->position >= strlen($GLOBALS[$this->varname]);
    }
    public function stream_seek($offset, $whence) {
        $l=strlen(&$GLOBALS[$this->varname]);
        $p=&$this->position;
        switch ($whence) {
            case SEEK_SET: $newPos = $offset; break;
            case SEEK_CUR: $newPos = $p + $offset; break;
            case SEEK_END: $newPos = $l + $offset; break;
            default: return false;
        }
        $ret = ($newPos >=0 && $newPos <=$l);
        if ($ret) $p=$newPos;
        return $ret;
    }
}

stream_wrapper_register("var", "VariableStream");
$csv = "foo,bar\ntest,1,2,3\n";

$row = 1;
if (($handle = fopen("var://csv", "r")) !== FALSE) {
    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);
}
?>

Of course, for your particular example, there are simpler stream methods that can be used.

konforce
Nice answer, and technically the cleanest solution. Writing to a file first just seems wrong!
David Caunt