fopen 関数を使用して、ファイル名(実際のファイル、またはURL)から PHPストリーム を作成できることを知っています。
$stream = fopen('php://temp', 'r');
結果のストリーム($stream
)は、URLから作成されたタイプ "stream"のresourceですphp://temp
。
しかし、リソースから上記のようなストリームをどのように作成できますか?
なぜ私はこれを尋ねるのですか?
私は PSR-7 ライブラリに取り組んでおり、PSR-7 StreamInterface をStream
クラスで実装しました。 Stream
インスタンスを作成するために、StreamFactory
も実装することにしました。そのインターフェースStreamFactoryInterface
は PSR-17(HTTP Factories) 提案で定義されています。
StreamFactoryInterface
は、createStreamFromResource
という名前のメソッドを定義します。これは、その公式コメントに準拠しており、次の条件を満たす必要があります。
既存のリソースから新しいストリームを作成します。
ストリームは読み取り可能でなければならず、書き込み可能である必要があります。
したがって、ファクトリメソッドはリソースを引数として受け取ります。そして、その具体的な実装では、新しいStream
オブジェクトが作成されます-これも引数としてリソースを受け取ります。
これが問題です:
簡単にするために、Stream
クラスがストリームでのみ機能するとします。タイプ "stream"のリソースを使用します。タイプ "stream"ではないリソースを受信した場合、それは拒否されます。
では、createStreamFromResource
のリソース引数がまだタイプ "stream"のリソースでない場合はどうなるでしょうか。どうすればストリームに変換できますか。タイプ "stream"のリソースに入れて、それをさらに渡すことができるように、新しいStream
オブジェクトを作成するための呼び出しにそれを渡しますか?このタスクを達成する方法(PHPメソッド、関数、またはキャスト関数)はありますか?
注:
testStream.php
)を用意しました。 Stream
インスタンス、3つの方法:直接1回、およびストリームファクトリを使用して2回。StreamFactory
、メソッドcreateStreamFromResource
も投稿します。このメソッドの呼び出しは、testStream.php
でストリームを作成する4番目の方法です。Stream
とResponse
を提示して、必要に応じてすべてを直接テストできるようにします。 2つのクラスは、私の実際のコードを非常に単純化したバージョンです。時間と忍耐をありがとうございました!
<?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"?
*/
//...
<?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);
}
}
<?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() {}
}
<?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 = '') {}
}
渡された引数の処理方法は、最終的な実装によって異なります。コードがストリーム引数を期待している場合は、そのようなものを検出しないときに停止する必要があります。ただし、コードが問題を処理することが期待される場合は、ストリームの作成を試みることができます。
編集
最初から取得できませんでしたが、リソース変数を変換できるかどうかが問題だったようです。 documentation によると、これは不可能であり、意味がありません。
PSR-7には非常に優れた実装がかなりありますStreamInterface
最初に検討することをお勧めします。どのような検証とロジックを実行する必要があるかについて、いくつかのアイデアを得るかもしれません。
StreamInterface
更新:これらすべてのリンクを調べた後、私はあなたの現在のコードにいくつかの問題を見つけました:
コンストラクタでリソースタイプを確認する必要があります。たとえば、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, '+')
);
}
最初にread
およびseek
を実装する必要があるisSeekable
およびisReadable
関数と同じです。
__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);
}