我知道我可以使用fopen函数从文件名(真实文件名或 URL)创建PHP 流:
$stream = fopen('php://temp', 'r');
生成的流 ( $stream
) 是从 URL 创建的“流”类型的资源。php://temp
但是我怎样才能从资源中创建一个像上面这样的流呢?
我为什么要问这个?
我正在开发一个PSR-7库,并且我用一个类实现了 PSR-7 StreamInterface 。Stream
为了创建Stream
实例,我决定也实现一个StreamFactory
。它的接口 ,StreamFactoryInterface
是在PSR-17(HTTP 工厂)提案中定义的。
StreamFactoryInterface
定义了一个名为 的方法,该方法createStreamFromResource
- 符合其官方评论 - 应该:
从现有资源创建新流。
流必须是可读的并且可能是可写的。
所以工厂方法接收一个资源作为参数。并且,在其具体实现中,Stream
创建了一个新对象——它也接收一个资源作为参数。
这是问题所在:
为简单起见,假设Stream
该类仅适用于流,例如“流”类型的资源。如果它接收到的资源不是"stream"类型的,它会拒绝它。
那么,如果资源参数createStreamFromResource
还不是“流”类型的资源呢?如何将其转换为流,例如转换为"stream"类型的资源,以便我可以进一步将其传递给Stream
使用它创建新对象的调用?有没有办法(PHP 方法、函数或强制转换函数)来完成这项任务?
笔记:
- 为清楚起见,我准备了一个完整的示例
testStream.php
(Stream
- 我还发布了工厂接口的具体实现:
StreamFactory
带有方法的类createStreamFromResource
。调用此方法应该是我在testStream.php
. - 此外,我还介绍了类
Stream
和Response
,以便您可以根据需要直接测试所有内容。这两个类是我真实代码的一个非常简化的版本。 - 在我的代码中,我用"@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"?
*/
//...
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);
}
}
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() {}
}
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 = '') {}
}