在PHP中有没有一种方法可以将字符串作为文件句柄访问?

12

我所在的服务器只支持 PHP 5.2.6,因此无法使用 str_getcsv 函数,而需要使用 fgetcsv 函数。但是,该函数要求操作的文件必须是通过 fopen()、popen() 或 fsockopen() 成功打开的有效文件指针。

我的问题是:是否有一种方法可以将字符串作为文件指针来访问?

另一种选择是将字符串写入文本文件中,然后通过 fopen() 访问它,再使用 fgetcsv 函数进行操作,但我希望能够像 perl 语言那样直接操作。

5个回答

26

如果你查看str_getcsv的手册页面上的用户注释,你会发现来自daniel的这个注释,其中提出了以下函数(引用)

<?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;
    }
}
?>

它似乎正在按照您的要求执行:使用指向内存中临时文件句柄的流,对其使用fgetcsv


有关文档,请参见PHP输入/输出流,其中包括php://temp流包装器。


当然,您应该测试它是否适用于您--但至少,这应该给您一个实现此目的的想法 ;-)


1
如果可以的话,我会给这个点赞两次 - 你总是有正好的答案。我需要更经常地阅读用户注释。 - Erik
谢谢 :-) ;;; 用户的笔记经常提供一些有趣的信息 :-) (嗯,如果我们中的某一个遇到了问题,那么很可能其他人之前已经遇到过相同的问题^^) - Pascal MARTIN
有趣的是,在阅读了一些资料后,你可以直接使用 php://memory - php://temp 的唯一优点是当文件超过指定大小时,它会写入磁盘;如果你的文件大小在其下,它将完全保存在内存中。 - Erik
@Erik:我必须承认,我从未遇到过达到那个大小限制的情况,但还是谢谢你的提醒,这确实很好知道! - Pascal MARTIN
我经常看到fopen(“php://output”,'r+');被使用,但是您的解决方案更好,因为它不会干扰主输出。 - Liam

10

我很震惊没有人回答这个解决方案:

<?php

$string = "I tried, honestly!";
$fp     = fopen('data://text/plain,' . $string,'r');

echo stream_get_contents($fp);

#fputcsv($fp, .......);

?>

一种内存占用较高的完美解决方案:

<?php

class StringStream
{
    private   $Variable = NULL;
    protected $fp       = 0;

    final public function __construct(&$String, $Mode = 'r')
    {
        $this->$Variable = &$String;

        switch($Mode)
        {
            case 'r':
            case 'r+':
                $this->fp = fopen('php://memory','r+');
                fwrite($this->fp, @strval($String));
                rewind($this->fp);
                break;

            case 'a':
            case 'a+':
                $this->fp = fopen('php://memory','r+');
                fwrite($this->fp, @strval($String));
                break;

            default:
                $this->fp = fopen('php://memory',$Mode);
        }
    }

    final public function flush()
    {
        # Update variable
        $this->Variable = stream_get_contents($this->fp);
    }

    final public function __destruct()
    {
        # Update variable on destruction;
        $this->Variable = stream_get_contents($this->fp);
    }

    public function __get($name)
    {
        switch($name)
        {
            case 'fp': return $fp;
            default:   trigger error('Undefined property: ('.$name.').');
        }

        return NULL;
    }
}

$string = 'Some bad-ass string';
$stream = new StringStream($string);

echo stream_get_contents($stream->fp);
#fputcsv($stream->fp, .......);

?>


在您的第一个 data:// 方法中,提供的 $string 在被输入到 data:// 之前应该进行百分号编码。请参阅 https://www.php.net/manual/en/wrappers.data.php#100979。 - Jaime Hablutzel

6
回答您的一般问题,是的,您可以将变量视为文件流。

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

以下内容是从PHP手册的几个不同评论中复制并粘贴而来(因此我无法保证它的生产就绪性):
<?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);
}
?>

当然,针对您的特定示例,可以使用更简单的流方法。

很好的答案,从技术上讲是最干净的解决方案。先写入文件似乎不太对! - David Snabel-Caunt
很遗憾,您将不得不使用全局变量。为什么不使用上下文来添加使用任何变量而不仅仅是全局变量的能力呢? - mAsT3RpEE

1

您可以使用流处理程序,例如 php://memory 实现您要完成的操作。只需打开、fwrite、rewind,然后您应该能够使用 fgetcsv。


-3

很遗憾,这是不可能的。您不能将字符串视为文件流。您确实需要先将字符串写入文件,然后使用fopen打开该文件。

现在显而易见的是,您考虑过升级吗?


客户端的服务器,不是我的服务器。共享托管环境,等等等...... 当然,我在自己的服务器上运行最新版本。 - Erik
:) 不知道这些,谢谢提供链接!我想每天都能学到新东西。不过显然升级仍然是最好的解决方案。 - Aistina

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接