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