You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从资源创建PHP流?PSR-7 Stream类开发技术问询

How to Create a PSR-7 Stream from an Existing PHP Resource

Great question! When building a PSR-7 library, wrapping an existing PHP stream resource into your custom Stream class (that implements StreamInterface) is a common need. The core idea is to delegate all the PSR-7 stream methods to the underlying PHP resource while handling validation and error checking properly. Here's a step-by-step guide to implement this:

Core Approach

To turn an existing resource into a PSR-7 compliant stream:

  1. Validate the resource: Ensure it’s a valid PHP stream resource before proceeding.
  2. Extract metadata: Use stream_get_meta_data() to pull critical info like the stream’s mode, seekability, and URI.
  3. Delegate methods: Implement every StreamInterface method by calling the corresponding PHP stream function (e.g., fread() for read(), fseek() for seek()).

Example Implementation

Here’s how to extend your existing Stream class with a static factory method to wrap resources, plus key method implementations:

use Psr\Http\Message\StreamInterface;

class Stream implements StreamInterface
{
    /** @var resource|null The underlying PHP stream resource */
    private $resource;

    /** @var array Stream metadata (from stream_get_meta_data) */
    private $metadata;

    // Your existing constructor (if any) can remain, or adjust to accept resources directly
    private function __construct() {}

    /**
     * Create a Stream instance from an existing PHP stream resource.
     *
     * @param resource $resource A valid PHP stream resource.
     * @return self
     * @throws \InvalidArgumentException If the input isn't a valid stream resource.
     */
    public static function fromResource($resource): self
    {
        // Validate the input is a stream resource
        if (!is_resource($resource) || get_resource_type($resource) !== 'stream') {
            throw new \InvalidArgumentException('Provided value must be a valid PHP stream resource.');
        }

        $stream = new self();
        $stream->resource = $resource;
        $stream->metadata = stream_get_meta_data($resource);

        return $stream;
    }

    // --- StreamInterface Method Implementations ---

    public function read(int $length): string
    {
        if (!$this->resource) {
            throw new \RuntimeException('Cannot read from a closed stream.');
        }

        $data = fread($this->resource, $length);
        if ($data === false) {
            throw new \RuntimeException('Failed to read data from the stream.');
        }

        return $data;
    }

    public function write(string $string): int
    {
        if (!$this->resource) {
            throw new \RuntimeException('Cannot write to a closed stream.');
        }

        $bytesWritten = fwrite($this->resource, $string);
        if ($bytesWritten === false) {
            throw new \RuntimeException('Failed to write data to the stream.');
        }

        return $bytesWritten;
    }

    public function seek(int $offset, int $whence = SEEK_SET): void
    {
        if (!$this->resource) {
            throw new \RuntimeException('Cannot seek a closed stream.');
        }

        if (!$this->isSeekable()) {
            throw new \RuntimeException('This stream is not seekable.');
        }

        $result = fseek($this->resource, $offset, $whence);
        if ($result !== 0) {
            throw new \RuntimeException('Failed to seek to the specified position.');
        }
    }

    public function tell(): int
    {
        if (!$this->resource) {
            throw new \RuntimeException('Cannot get position of a closed stream.');
        }

        $position = ftell($this->resource);
        if ($position === false) {
            throw new \RuntimeException('Failed to retrieve stream position.');
        }

        return $position;
    }

    public function eof(): bool
    {
        return !$this->resource || feof($this->resource);
    }

    public function close(): void
    {
        if ($this->resource) {
            fclose($this->resource);
            $this->resource = null;
            $this->metadata = [];
        }
    }

    public function isReadable(): bool
    {
        if (!$this->resource) return false;
        $mode = $this->metadata['mode'] ?? '';
        return strpos($mode, 'r') !== false || strpos($mode, '+') !== false;
    }

    public function isWritable(): bool
    {
        if (!$this->resource) return false;
        $mode = $this->metadata['mode'] ?? '';
        return strpos($mode, 'w') !== false || strpos($mode, 'a') !== false || strpos($mode, '+') !== false;
    }

    public function isSeekable(): bool
    {
        return isset($this->metadata['seekable']) && $this->metadata['seekable'];
    }

    public function getMetadata($key = null)
    {
        if ($key === null) return $this->metadata;
        return $this->metadata[$key] ?? null;
    }

    // Implement remaining StreamInterface methods (getContents(), getSize(), etc.)
    // For getSize(), you can use fstat($this->resource)['size'] if available

    public function getContents(): string
    {
        if (!$this->resource) throw new \RuntimeException('Cannot read contents of a closed stream.');
        $contents = stream_get_contents($this->resource);
        if ($contents === false) throw new \RuntimeException('Failed to read stream contents.');
        return $contents;
    }

    // Add a destructor to auto-close the resource when the Stream instance is garbage-collected
    public function __destruct()
    {
        $this->close();
    }
}

How to Use It

Once you have this factory method, wrapping an existing resource is straightforward:

// Wrap a local file stream
$fileResource = fopen('/path/to/your/file.txt', 'r');
$fileStream = Stream::fromResource($fileResource);

// Wrap a php://temp stream (like your example)
$tempResource = fopen('php://temp', 'r+');
$tempStream = Stream::fromResource($tempResource);

Key Considerations

  • Resource Cleanup: The __destruct() method ensures the underlying resource is closed when the Stream instance is no longer needed, preventing resource leaks.
  • Error Handling: All methods throw appropriate exceptions as required by PSR-7, making your stream implementation robust.
  • Metadata Accuracy: Using stream_get_meta_data() ensures your stream correctly reports its capabilities (readable, writable, seekable) based on the original resource’s mode.

内容的提问来源于stack exchange,提问作者user9455607

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 07:17:35