1<?php
2
3declare(strict_types=1);
4
5namespace GuzzleHttp\Psr7;
6
7use Psr\Http\Message\StreamInterface;
8
9/**
10 * Stream decorator trait
11 *
12 * @property StreamInterface $stream
13 */
14trait StreamDecoratorTrait
15{
16    /**
17     * @param StreamInterface $stream Stream to decorate
18     */
19    public function __construct(StreamInterface $stream)
20    {
21        $this->stream = $stream;
22    }
23
24    /**
25     * Magic method used to create a new stream if streams are not added in
26     * the constructor of a decorator (e.g., LazyOpenStream).
27     *
28     * @return StreamInterface
29     */
30    public function __get(string $name)
31    {
32        if ($name === 'stream') {
33            $this->stream = $this->createStream();
34
35            return $this->stream;
36        }
37
38        throw new \UnexpectedValueException("$name not found on class");
39    }
40
41    public function __toString(): string
42    {
43        try {
44            if ($this->isSeekable()) {
45                $this->seek(0);
46            }
47
48            return $this->getContents();
49        } catch (\Throwable $e) {
50            if (\PHP_VERSION_ID >= 70400) {
51                throw $e;
52            }
53            trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR);
54
55            return '';
56        }
57    }
58
59    public function getContents(): string
60    {
61        return Utils::copyToString($this);
62    }
63
64    /**
65     * Allow decorators to implement custom methods
66     *
67     * @return mixed
68     */
69    public function __call(string $method, array $args)
70    {
71        /** @var callable $callable */
72        $callable = [$this->stream, $method];
73        $result = ($callable)(...$args);
74
75        // Always return the wrapped object if the result is a return $this
76        return $result === $this->stream ? $this : $result;
77    }
78
79    public function close(): void
80    {
81        $this->stream->close();
82    }
83
84    /**
85     * @return mixed
86     */
87    public function getMetadata($key = null)
88    {
89        return $this->stream->getMetadata($key);
90    }
91
92    public function detach()
93    {
94        return $this->stream->detach();
95    }
96
97    public function getSize(): ?int
98    {
99        return $this->stream->getSize();
100    }
101
102    public function eof(): bool
103    {
104        return $this->stream->eof();
105    }
106
107    public function tell(): int
108    {
109        return $this->stream->tell();
110    }
111
112    public function isReadable(): bool
113    {
114        return $this->stream->isReadable();
115    }
116
117    public function isWritable(): bool
118    {
119        return $this->stream->isWritable();
120    }
121
122    public function isSeekable(): bool
123    {
124        return $this->stream->isSeekable();
125    }
126
127    public function rewind(): void
128    {
129        $this->seek(0);
130    }
131
132    public function seek($offset, $whence = SEEK_SET): void
133    {
134        $this->stream->seek($offset, $whence);
135    }
136
137    public function read($length): string
138    {
139        return $this->stream->read($length);
140    }
141
142    public function write($string): int
143    {
144        return $this->stream->write($string);
145    }
146
147    /**
148     * Implement in subclasses to dynamically create streams when requested.
149     *
150     * @throws \BadMethodCallException
151     */
152    protected function createStream(): StreamInterface
153    {
154        throw new \BadMethodCallException('Not implemented');
155    }
156}
157