/library/Rediska/Connection.php

https://github.com/netweaver/Rediska · PHP · 446 lines · 374 code · 20 blank · 52 comment · 19 complexity · 1ddba4da499ec92a9f064bf912cd6487 MD5 · raw file

  1. <?php
  2. /**
  3. * Rediska connection
  4. *
  5. * @author Ivan Shumkov
  6. * @package Rediska
  7. * @subpackage Connection
  8. * @version @package_version@
  9. * @link http://rediska.geometria-lab.net
  10. * @license http://www.opensource.org/licenses/bsd-license.php
  11. */
  12. class Rediska_Connection extends Rediska_Options
  13. {
  14. const DEFAULT_HOST = '127.0.0.1';
  15. const DEFAULT_PORT = 6379;
  16. const DEFAULT_WEIGHT = 1;
  17. const DEFAULT_DB = 0;
  18. /**
  19. * Socket
  20. *
  21. * @var stream
  22. */
  23. protected $_socket;
  24. /**
  25. * Options
  26. *
  27. * host - Redis server host. For default 127.0.0.1
  28. * port - Redis server port. For default 6379
  29. * db - Redis server DB index. For default 0
  30. * alias - Redis server alias for operate keys on specified server. For default [host]:[port]
  31. * weight - Weight of Redis server for key distribution. For default 1
  32. * password - Redis server password. Optional
  33. * persistent - Persistent connection to Redis server. For default false
  34. * timeout - Connection timeout for Redis server. Optional
  35. * readTimeout - Read timeout for Redis server
  36. * blockingMode - Blocking/non-blocking mode for reads
  37. *
  38. * @var array
  39. */
  40. protected $_options = array(
  41. 'host' => self::DEFAULT_HOST,
  42. 'port' => self::DEFAULT_PORT,
  43. 'db' => self::DEFAULT_DB,
  44. 'alias' => null,
  45. 'weight' => self::DEFAULT_WEIGHT,
  46. 'password' => null,
  47. 'persistent' => false,
  48. 'timeout' => null,
  49. 'readTimeout' => null,
  50. 'blockingMode' => true,
  51. 'streamContext' => null,
  52. );
  53. /**
  54. * Connect to redis server
  55. *
  56. * @throws Rediska_Connection_Exception
  57. * @return boolean
  58. * @uses self::getStreamContext()
  59. */
  60. public function connect()
  61. {
  62. if (!$this->isConnected()) {
  63. $socketAddress = 'tcp://' . $this->getHost() . ':' . $this->getPort();
  64. if ($this->_options['persistent']) {
  65. $flag = STREAM_CLIENT_PERSISTENT | STREAM_CLIENT_CONNECT;
  66. } else {
  67. $flag = STREAM_CLIENT_CONNECT;
  68. }
  69. $socketParams = array(
  70. $socketAddress,
  71. &$errno,
  72. &$errmsg,
  73. $this->getTimeout(),
  74. $flag
  75. );
  76. $streamContext = $this->getStreamContext();
  77. if ($streamContext) {
  78. $socketParams[] = $streamContext;
  79. }
  80. $this->_socket = call_user_func_array('stream_socket_client', $socketParams);
  81. // Throw exception if can't connect
  82. if (!is_resource($this->_socket)) {
  83. $msg = "Can't connect to Redis server on {$this->getHost()}:{$this->getPort()}";
  84. if ($errno || $errmsg) {
  85. $msg .= "," . ($errno ? " error $errno" : "") . ($errmsg ? " $errmsg" : "");
  86. }
  87. $this->_socket = null;
  88. throw new Rediska_Connection_Exception($msg);
  89. }
  90. // Set read timeout
  91. if ($this->_options['readTimeout'] != null) {
  92. $this->setReadTimeout($this->_options['readTimeout']);
  93. }
  94. // Set blocking mode
  95. if ($this->_options['blockingMode'] == false) {
  96. $this->setBlockingMode($this->_options['blockingMode']);
  97. }
  98. // Send password
  99. if ($this->getPassword() != '') {
  100. $auth = new Rediska_Connection_Exec($this, "AUTH {$this->getPassword()}");
  101. try {
  102. $auth->execute();
  103. } catch (Rediska_Command_Exception $e) {
  104. throw new Rediska_Connection_Exception("Password error: {$e->getMessage()}");
  105. }
  106. }
  107. // Set db
  108. if ($this->_options['db'] !== self::DEFAULT_DB) {
  109. $selectDb = new Rediska_Connection_Exec($this, "SELECT {$this->_options['db']}");
  110. try {
  111. $selectDb->execute();
  112. } catch (Rediska_Command_Exception $e) {
  113. throw new Rediska_Connection_Exception("Select db error: {$e->getMessage()}");
  114. }
  115. }
  116. return true;
  117. } else {
  118. return false;
  119. }
  120. }
  121. /**
  122. * Disconnect
  123. *
  124. * @return boolean
  125. */
  126. public function disconnect()
  127. {
  128. if ($this->isConnected()) {
  129. @fclose($this->_socket);
  130. return true;
  131. } else {
  132. return false;
  133. }
  134. }
  135. /**
  136. * Is connected
  137. *
  138. * @return boolean
  139. */
  140. public function isConnected()
  141. {
  142. return is_resource($this->_socket);
  143. }
  144. /**
  145. * Write to connection stream
  146. *
  147. * @param $string
  148. * @return boolean
  149. */
  150. public function write($string)
  151. {
  152. if ($string !== '') {
  153. $string = (string)$string . Rediska::EOL;
  154. $this->connect();
  155. while ($string) {
  156. $bytes = @fwrite($this->_socket, $string);
  157. if ($bytes === false) {
  158. $this->disconnect();
  159. throw new Rediska_Connection_Exception("Can't write to socket.");
  160. }
  161. if ($bytes == 0) {
  162. return true;
  163. }
  164. $string = substr($string, $bytes);
  165. }
  166. return true;
  167. } else {
  168. return false;
  169. }
  170. }
  171. /**
  172. * Read length bytes from connection stram
  173. *
  174. * @throws Rediska_Connection_Exception
  175. * @param integer $length
  176. * @return boolean
  177. */
  178. public function read($length)
  179. {
  180. if (!$this->isConnected()) {
  181. throw new Rediska_Connection_Exception("Can't read without connection to Redis server. Do connect or write first.");
  182. }
  183. if ($length > 0) {
  184. $data = $this->_readAndThrowException($length);
  185. } else {
  186. $data = null;
  187. }
  188. if ($length !== -1) {
  189. $this->_readAndThrowException(2);
  190. }
  191. return $data;
  192. }
  193. /**
  194. * Read line from connection stream
  195. *
  196. * @throws Rediska_Connection_Exception
  197. * @throws Rediska_Connection_TimeoutException
  198. * @return string
  199. */
  200. public function readLine()
  201. {
  202. if (!$this->isConnected()) {
  203. throw new Rediska_Connection_Exception("Can't read without connection to Redis server. Do connect or write first.");
  204. }
  205. $reply = @fgets($this->_socket);
  206. $info = stream_get_meta_data($this->_socket);
  207. if ($info['timed_out']) {
  208. throw new Rediska_Connection_TimeoutException("Connection read timed out.");
  209. }
  210. if ($reply === false) {
  211. if ($this->_options['blockingMode'] || (!$this->_options['blockingMode'] && $info['eof'])) {
  212. $this->disconnect();
  213. throw new Rediska_Connection_Exception("Can't read from socket.");
  214. }
  215. $reply = null;
  216. } else {
  217. $reply = trim($reply);
  218. }
  219. return $reply;
  220. }
  221. /**
  222. * Set read timeout
  223. *
  224. * @param $timeout
  225. * @return Rediska_Connection
  226. */
  227. public function setReadTimeout($timeout)
  228. {
  229. $this->_options['readTimeout'] = $timeout;
  230. if ($this->isConnected()) {
  231. $seconds = floor($this->_options['readTimeout']);
  232. $microseconds = ($this->_options['readTimeout'] - $seconds) * 1000000;
  233. stream_set_timeout($this->_socket, $seconds, $microseconds);
  234. }
  235. return $this;
  236. }
  237. /**
  238. * Set blocking/non-blocking mode for reads
  239. *
  240. * @param $flag
  241. * @return Rediska_Connection
  242. */
  243. public function setBlockingMode($flag = true)
  244. {
  245. $this->_options['blockingMode'] = $flag;
  246. if ($this->isConnected()) {
  247. stream_set_blocking($this->_socket, $this->_options['blockingMode']);
  248. }
  249. return $this;
  250. }
  251. /**
  252. * Get option host
  253. *
  254. * @return string
  255. */
  256. public function getHost()
  257. {
  258. return $this->_options['host'];
  259. }
  260. /**
  261. * Get option port
  262. *
  263. * @return string
  264. */
  265. public function getPort()
  266. {
  267. return $this->_options['port'];
  268. }
  269. /**
  270. * Get option weight
  271. *
  272. * @return string
  273. */
  274. public function getWeight()
  275. {
  276. return $this->_options['weight'];
  277. }
  278. /**
  279. * Get option password
  280. *
  281. * @return string
  282. */
  283. public function getPassword()
  284. {
  285. return $this->_options['password'];
  286. }
  287. /**
  288. * Get option timout
  289. *
  290. * @return string
  291. */
  292. public function getTimeout()
  293. {
  294. if (null !== $this->_options['timeout']) {
  295. return $this->_options['timeout'];
  296. } else {
  297. return ini_get('default_socket_timeout');
  298. }
  299. }
  300. /**
  301. * Connection alias
  302. *
  303. * @return string
  304. */
  305. public function getAlias()
  306. {
  307. if ($this->_options['alias'] != '') {
  308. return $this->_options['alias'];
  309. } else {
  310. return $this->_options['host'] . ':' . $this->_options['port'];
  311. }
  312. }
  313. /**
  314. * If a stream context is provided, use it creating the socket.
  315. *
  316. * It's supported to provide either an array with options, or an already created
  317. * resource.
  318. *
  319. * @return mixed null or resource
  320. * @see self::connect()
  321. */
  322. public function getStreamContext()
  323. {
  324. if ($this->_options['streamContext'] !== null) {
  325. if (is_resource($this->_options['streamContext'])) {
  326. return $this->_options['streamContext'];
  327. }
  328. if (is_array($this->_options['streamContext'])) {
  329. return stream_context_create($this->_options['streamContext']);
  330. }
  331. }
  332. return null;
  333. }
  334. /**
  335. * Read and throw exception if somthing wrong
  336. *
  337. * @param $length Lenght of bytes to read
  338. * @return string
  339. */
  340. protected function _readAndThrowException($length)
  341. {
  342. $data = @stream_get_contents($this->_socket, $length);
  343. $info = stream_get_meta_data($this->_socket);
  344. if ($info['timed_out']) {
  345. throw new Rediska_Connection_TimeoutException("Connection read timed out.");
  346. }
  347. if ($data === false) {
  348. $this->disconnect();
  349. throw new Rediska_Connection_Exception("Can't read from socket.");
  350. }
  351. return $data;
  352. }
  353. /**
  354. * Magic method for execute command in connection
  355. *
  356. * @return string
  357. */
  358. public function __invoke($command)
  359. {
  360. $exec = new Rediska_Connection_Exec($this, $command);
  361. return $exec->execute();
  362. }
  363. /**
  364. * Return alias to strings
  365. */
  366. public function __toString()
  367. {
  368. return $this->getAlias();
  369. }
  370. /**
  371. * Disconnect on destrcuct connection object
  372. */
  373. public function __destruct()
  374. {
  375. if (!$this->_options['persistent']) {
  376. $this->disconnect();
  377. }
  378. }
  379. /**
  380. * Do not clone socket
  381. */
  382. public function __clone()
  383. {
  384. $this->_socket = null;
  385. }
  386. }