1<?php
2
3namespace GuzzleHttp\Psr7;
4
5use Psr\Http\Message\StreamInterface;
6
7/**
8 * Compose stream implementations based on a hash of functions.
9 *
10 * Allows for easy testing and extension of a provided stream without needing
11 * to create a concrete class for a simple extension point.
12 *
13 * @final
14 */
15class FnStream implements StreamInterface
16{
17    /** @var array */
18    private $methods;
19
20    /** @var array Methods that must be implemented in the given array */
21    private static $slots = ['__toString', 'close', 'detach', 'rewind',
22        'getSize', 'tell', 'eof', 'isSeekable', 'seek', 'isWritable', 'write',
23        'isReadable', 'read', 'getContents', 'getMetadata'];
24
25    /**
26     * @param array $methods Hash of method name to a callable.
27     */
28    public function __construct(array $methods)
29    {
30        $this->methods = $methods;
31
32        // Create the functions on the class
33        foreach ($methods as $name => $fn) {
34            $this->{'_fn_' . $name} = $fn;
35        }
36    }
37
38    /**
39     * Lazily determine which methods are not implemented.
40     *
41     * @throws \BadMethodCallException
42     */
43    public function __get($name)
44    {
45        throw new \BadMethodCallException(str_replace('_fn_', '', $name)
46            . '() is not implemented in the FnStream');
47    }
48
49    /**
50     * The close method is called on the underlying stream only if possible.
51     */
52    public function __destruct()
53    {
54        if (isset($this->_fn_close)) {
55            call_user_func($this->_fn_close);
56        }
57    }
58
59    /**
60     * An unserialize would allow the __destruct to run when the unserialized value goes out of scope.
61     *
62     * @throws \LogicException
63     */
64    public function __wakeup()
65    {
66        throw new \LogicException('FnStream should never be unserialized');
67    }
68
69    /**
70     * Adds custom functionality to an underlying stream by intercepting
71     * specific method calls.
72     *
73     * @param StreamInterface $stream  Stream to decorate
74     * @param array           $methods Hash of method name to a closure
75     *
76     * @return FnStream
77     */
78    public static function decorate(StreamInterface $stream, array $methods)
79    {
80        // If any of the required methods were not provided, then simply
81        // proxy to the decorated stream.
82        foreach (array_diff(self::$slots, array_keys($methods)) as $diff) {
83            $methods[$diff] = [$stream, $diff];
84        }
85
86        return new self($methods);
87    }
88
89    public function __toString()
90    {
91        return call_user_func($this->_fn___toString);
92    }
93
94    public function close()
95    {
96        return call_user_func($this->_fn_close);
97    }
98
99    public function detach()
100    {
101        return call_user_func($this->_fn_detach);
102    }
103
104    public function getSize()
105    {
106        return call_user_func($this->_fn_getSize);
107    }
108
109    public function tell()
110    {
111        return call_user_func($this->_fn_tell);
112    }
113
114    public function eof()
115    {
116        return call_user_func($this->_fn_eof);
117    }
118
119    public function isSeekable()
120    {
121        return call_user_func($this->_fn_isSeekable);
122    }
123
124    public function rewind()
125    {
126        call_user_func($this->_fn_rewind);
127    }
128
129    public function seek($offset, $whence = SEEK_SET)
130    {
131        call_user_func($this->_fn_seek, $offset, $whence);
132    }
133
134    public function isWritable()
135    {
136        return call_user_func($this->_fn_isWritable);
137    }
138
139    public function write($string)
140    {
141        return call_user_func($this->_fn_write, $string);
142    }
143
144    public function isReadable()
145    {
146        return call_user_func($this->_fn_isReadable);
147    }
148
149    public function read($length)
150    {
151        return call_user_func($this->_fn_read, $length);
152    }
153
154    public function getContents()
155    {
156        return call_user_func($this->_fn_getContents);
157    }
158
159    public function getMetadata($key = null)
160    {
161        return call_user_func($this->_fn_getMetadata, $key);
162    }
163}
164