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