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