从资源创建流



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

$stream = fopen('php://temp', 'r');

然后,由此产生的流( $stream(为 resource '流',由url php://temp.

创建

,但是我如何从资源中创建像上面的流?


我为什么要问这个?

我正在使用PSR-7库,并使用Stream类实现了PSR-7流界面。为了创建Stream实例,我决定也实现StreamFactory。它的界面StreamFactoryInterface在PSR-17:HTTP工厂中定义。

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

从现有资源创建一个新流。

流必须可读并且可能是可写的。

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

这是问题:

为了简单起见,假设Stream类仅与流一起工作,例如具有类型的资源"流" 。如果它收到一个不属于 type stream'的资源,它拒绝它。

那么,如果createStreamFromResource的资源参数还不是类型"流"&quot'的资源怎么办?如何将其转换为流,例如进入类型的资源"流" ,这样我就可以将其传递给了使用它创建新的Stream对象的呼吁?有没有办法(php方法,函数或可能是铸造功能(来完成此任务?

注意:

  • 为了清楚起见,我准备了一个完整的示例(testStream.php(,说明了我如何创建流,例如Stream实例,分为三个方式:直接使用流式工厂两次。
  • 我还发布了工厂接口的具体实现:使用方法createStreamFromResource的类StreamFactory。对此方法的调用应该是我在testStream.php中创建流的第四种方式。
  • 此外,我介绍了StreamResponse类,以便您可以直接测试全部(如果需要(。这两个类是我的真实代码的非常简单的版本。
  • 在我的代码中,我用@询问。。

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


testStream.php(测试页面(:

<?php
use TestsStream;
use TestsResponse;
use TestsStreamFactory;
/*
 * ================================================
 * 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"?
 */
//...

流factfactory类(如我所拥有的,因此没有简化(:

<?php
namespace Tests;
use TestsStream;
use PsrHttpMessageStreamInterface;
use PsrHttpMessageStreamFactoryInterface;
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);
    }
}

流类(非常简化(:

<?php
namespace Tests;
use PsrHttpMessageStreamInterface;
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() {}
}

响应类(非常简化(:

<?php
namespace Tests;
use PsrHttpMessageStreamInterface;
use PsrHttpMessageResponseInterface;
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 = '') {}
}

我建议首先要查看PSR-7 StreamInterface的非常好的实现。您可能会了解需要做什么样的验证和逻辑。

  • guzzle/psr7- PSR -7 StreamInterface
  • 的Guzzle实现
  • ReactPHP/流 - 这不是实现PSR -7,但是这些家伙在实施中提出了很多想法,并且代码有很好的记录。查看ReadableReresourcestream和WritableReSourceStream。
  • zendframework/zend-diactoros
  • Slimphp/Slim

更新:查看了所有这些链接后,我发现了您当前代码的一些问题:

  • 您必须检查构造函数中的资源类型。例如,它可能是MySQL资源,您不想写信:

    public function __construct($stream, string $accessMode = 'r') {
        if (is_string($stream)) {
            $stream = fopen($stream, $accessMode);
        }
        if (! is_resource($stream) || 'stream' !== get_resource_type($stream)) {
            throw new InvalidArgumentException(
                'Invalid stream provided; must be a string stream identifier or stream resource'
            );
        }
        $this->stream = $stream;
    }
    
  • 当您写入流时,检查流是否真正可写。您必须先实现isWritable方法,然后在write函数中调用它。此示例来自Zend-Diactoros库:

    public function isWritable()
    {
        if (! $this->resource) {
            return false;
        }
        $meta = stream_get_meta_data($this->resource);
        $mode = $meta['mode'];
        return (
            strstr($mode, 'x')
            || strstr($mode, 'w')
            || strstr($mode, 'c')
            || strstr($mode, 'a')
            || strstr($mode, '+')
        );
    }
    
  • readseek相同的功能,您必须先实现isSeekableisReadable

  • __toString还应检查流是否可读且可寻求:

    public function __toString()
    {
        if (! $this->isReadable()) {
            return '';
        }
        try {
            if ($this->isSeekable()) {
                $this->rewind();
            }
            return $this->getContents();
        } catch (RuntimeException $e) {
            return '';
        }
    } 
    

希望这会有所帮助。祝您新库好运。

如何处理传递的参数取决于您的最终实现。如果您的代码期望一个流参数,那么当它检测到没有此类内容时,它应该停止。但是,如果您的代码应处理问题,则可以尝试创建流。

编辑

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

您可以以任何方式实现它,但是此方法本质上只是预先生成的资源的包装器。

在大多数情况下,您的流可能会接入字符串和可能的设置/选项数组,并从信息中创建一个流(可能是沿途的fopen('http://...')

createStreamFromResource($ resource(将获得预先生成的资源(例如,来自fopen的返回资源值,而不是数据执行fopen(:

class Stream implements StreamInterface {
    // ...
    public function __construct($url, $opt = null) {
        // ...
        if( is_resource( $url ) ) {
            /*
             * Check that the $resource is a valid resource
             * (e.g. an http request from an fopen call vs a mysql resource.)
             * or possibly a stream context that still needs to create a
             * request...
             */
            if( !$isValid ) {
                return false;
            }
            $this->resource = $resource;
        } else {
            // ...
            $this->resource = fopen($url, $modifiedOpt);
            // ...
        }
    }
    // ...
    /* createStreamFromResource would call Stream::fromResource($r)
     * or possibly Stream($resource) directly, your call.
     */
    static function fromResource($resource) {
        return new static($resource);
    }
}

您的工厂方法可能很简单:

public function createStreamFromResource($resource) {
    return Stream::fromResource($resource);
}

相关内容

  • 没有找到相关文章

最新更新