从资源创建流

dak*_*kis 10 php fopen stream php-stream-wrappers psr-7

我知道我可以使用fopen函数从文件名(真实的或URL)创建PHP流:

$stream = fopen('php://temp', 'r');
Run Code Online (Sandbox Code Playgroud)

然后,生成的stream($stream)是从URL创建的"stream"类型的资源.php://temp

但是我如何从资源中创建如上所述的流?


我为什么这么问?

我正在研究PSR-7库,我用类实现了PSR-7 StreamInterfaceStream.为了创建Stream实例,我决定也实现了StreamFactory.其接口StreamFactoryInterfacePSR-17(HTTP工厂)提案中定义.

StreamFactoryInterface定义了一个名为方法createStreamFromResource,其中-符合其官方评论-应该:

从现有资源创建新流.

流必须是可读的并且可以是可写的.

因此工厂方法接收资源作为参数.并且,在其具体实现中,Stream创建了一个新对象 - 它也接收资源作为参数.

这是问题所在:

为简单起见,假设Stream该类仅适用于流,例如使用"stream"类型的资源.如果它收到的资源不属于"stream"类型,则会拒绝它.

那么,如果资源参数createStreamFromResource不是"流"类型的资源呢?如何将其转换为流,例如转换为"stream"类型的资源,以便我可以将其进一步传递给Stream使用它创建新对象的调用?是否有实现此任务的方法(PHP方法,函数或可能的转换函数)?

笔记:

  • 为清楚起见,我准备了一个完整的示例(testStream.php),我Stream用三种方式创建一个流,例如一个实例:一次直接,两次使用流工厂.
  • 我还发布了工厂接口的具体实现:StreamFactory带有方法的类createStreamFromResource.调用此方法应该是我创建流的第四种方法testStream.php.
  • 此外,我目前的课程StreamResponse,这样就可以直接测试所有,如果你想.这两个类是我的真实代码的一个非常简化的版本.
  • 在我的代码中,我用"@asking"标记了两个提问地点.

非常感谢您的时间和耐心!


testStream.php(测试页面)

<?php

use Tests\Stream;
use Tests\Response;
use Tests\StreamFactory;

/*
 * ================================================
 * Option 1: Create a stream by a stream name
 * (like "php://temp") with read and write rights.
 * ================================================
 */
$stream = new Stream('php://temp', 'w+b');

$response = new Response($stream);
$response->getBody()->write(
        'Stream 1: Created directly.<br/><br/>'
);
echo $response->getBody();

/*
 * ================================================
 * Option 2: Create a stream by a stream name
 * (like "php://temp"), using a stream factory.
 * ================================================
 */
$streamFactory = new StreamFactory();
$stream = $streamFactory->createStreamFromFile('php://temp', 'w+b');

$response = new Response($stream);
$response->getBody()->write(
        'Stream 2: Created by a stream name, with a stream factory.<br/><br/>'
);
echo $response->getBody();

/*
 * ================================================
 * Option 3: Create a stream from a string, using a
 * stream factory.
 * ================================================
 */
$streamFactory = new StreamFactory();
$stream = $streamFactory->createStream(
        'Stream 3: Created from a string, with a stream factory.<br/><br/>'
);

$response = new Response($stream);
echo $response->getBody();

/*
 * ================================================
 * Option 4: Create a stream from an existing
 * resource, using a stream factory.
 * ================================================
 * 
 * @asking How can I create a stream by calling the
 * the factory method ServerFactory::createStreamFromResource
 * with a resource which is not of type "stream"?
 */
//...
Run Code Online (Sandbox Code Playgroud)

StreamFactory类(因为我有它,所以没有简化)

<?php

namespace Tests;

use Tests\Stream;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\StreamFactoryInterface;

class StreamFactory implements StreamFactoryInterface {

    /**
     * Create a new stream from an existing resource.
     *
     * The stream MUST be readable and may be writable.
     *
     * @param resource $resource
     *
     * @return StreamInterface
     * @throws \InvalidArgumentException
     */
    public function createStreamFromResource($resource) {
        /*
         * @asking What if $resource is not already a resource of type *"stream"*? 
         * How can I transform it into a stream, e.g. into a resource of type *"stream"*, 
         * so that I can pass it further, to the call for creating a new `Stream` object 
         * with it? Is there a way (a PHP method, a function, or maybe a casting function) 
         * of achieving this task?
         */
         //...

        return new Stream($resource, 'w+b');
    }

    /**
     * Create a new stream from a string.
     *
     * The stream SHOULD be created with a temporary resource.
     *
     * @param string $content
     *
     * @return StreamInterface
     * @throws \InvalidArgumentException
     */
    public function createStream($content = '') {
        if (!isset($content) || !is_string($content)) {
            throw new \InvalidArgumentException('For creating a stream, a content string must be provided!');
        }

        $stream = $this->createStreamFromFile('php://temp', 'w+b');

        $stream->write($content);

        return $stream;
    }

    /**
     * Create a stream from an existing file.
     *
     * The file MUST be opened using the given mode, which may be any mode
     * supported by the `fopen` function.
     *
     * The `$filename` MAY be any string supported by `fopen()`.
     *
     * @param string $filename
     * @param string $mode
     *
     * @return StreamInterface
     */
    public function createStreamFromFile($filename, $mode = 'r') {
        return new Stream($filename, $mode);
    }

}
Run Code Online (Sandbox Code Playgroud)

Stream类(非常简化)

<?php

namespace Tests;

use Psr\Http\Message\StreamInterface;

class Stream implements StreamInterface {

    /**
     * Stream (resource).
     *
     * @var resource
     */
    private $stream;

    /**
     *
     * @param string|resource $stream Stream name, or resource.
     * @param string $accessMode (optional) Access mode.
     * @throws \InvalidArgumentException
     */
    public function __construct($stream, string $accessMode = 'r') {
        if (
                !isset($stream) ||
                (!is_string($stream) && !is_resource($stream))
        ) {
            throw new \InvalidArgumentException(
                'The provided stream must be a filename, or an opened resource of type "stream"!'
            );
        }

        if (is_string($stream)) {
            $this->stream = fopen($stream, $accessMode);
        } elseif (is_resource($stream)) {
            if ('stream' !== get_resource_type($stream)) {
                throw new \InvalidArgumentException('The provided resource must be of type "stream"!');
            }

            $this->stream = $stream;
        }
    }

    /**
     * Write data to the stream.
     *
     * @param string $string The string that is to be written.
     * @return int Returns the number of bytes written to the stream.
     * @throws \RuntimeException on failure.
     */
    public function write($string) {
        return fwrite($this->stream, $string);
    }

    /**
     * Reads all data from the stream into a string, from the beginning to end.
     *
     * @return string
     */
    public function __toString() {
        try {
            // Rewind the stream.
            fseek($this->stream, 0);

            // Get the stream contents as string.
            $contents = stream_get_contents($this->stream);

            return $contents;
        } catch (\RuntimeException $exc) {
            return '';
        }
    }

    public function close() {}
    public function detach() {}
    public function eof() {}
    public function getContents() {}
    public function getMetadata($key = null) {}
    public function getSize() {}
    public function isReadable() {}
    public function isSeekable() {}
    public function isWritable() {}
    public function read($length) {}
    public function rewind() {}
    public function seek($offset, $whence = SEEK_SET) {}
    public function tell() {}

}
Run Code Online (Sandbox Code Playgroud)

Response类(非常简化)

<?php

namespace Tests;

use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\ResponseInterface;

class Response implements ResponseInterface {

    /**
     *
     * @param StreamInterface $body Message body.
     */
    public function __construct(StreamInterface $body) {
        $this->body = $body;
    }

    /**
     * Gets the body of the message.
     *
     * @return StreamInterface Returns the body as a stream.
     */
    public function getBody() {
        return $this->body;
    }

    public function getHeader($name) {}
    public function getHeaderLine($name) {}
    public function getHeaders() {}
    public function getProtocolVersion() {}
    public function hasHeader($name) {}
    public function withAddedHeader($name, $value) {}
    public function withBody(StreamInterface $body) {}
    public function withHeader($name, $value) {}
    public function withProtocolVersion($version) {}
    public function withoutHeader($name) {}
    public function getReasonPhrase() {}
    public function getStatusCode() {}
    public function withStatus($code, $reasonPhrase = '') {}

}
Run Code Online (Sandbox Code Playgroud)

Emi*_*mil 1

如何处理传递的参数取决于您的最终实现。如果您的代码需要一个流参数,那么当它没有检测到这样的东西时,它应该停止。但是,如果您的代码预计可以处理该问题,那么您可以尝试创建一个流。

编辑

从一开始就没有得到它,但看起来问题是是否可以转换资源变量。根据文档,这是不可能的,也没有意义。