PageRenderTime 41ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Replication/ReplicationStrategy.php

http://github.com/nrk/predis
PHP | 304 lines | 188 code | 28 blank | 88 comment | 22 complexity | dc7af4d5c9308a3ce390907c589bbba7 MD5 | raw file
  1. <?php
  2. /*
  3. * This file is part of the Predis package.
  4. *
  5. * (c) Daniele Alessandri <suppakilla@gmail.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Predis\Replication;
  11. use Predis\Command\CommandInterface;
  12. use Predis\NotSupportedException;
  13. /**
  14. * Defines a strategy for master/slave replication.
  15. *
  16. * @author Daniele Alessandri <suppakilla@gmail.com>
  17. */
  18. class ReplicationStrategy
  19. {
  20. protected $disallowed;
  21. protected $readonly;
  22. protected $readonlySHA1;
  23. /**
  24. *
  25. */
  26. public function __construct()
  27. {
  28. $this->disallowed = $this->getDisallowedOperations();
  29. $this->readonly = $this->getReadOnlyOperations();
  30. $this->readonlySHA1 = array();
  31. }
  32. /**
  33. * Returns if the specified command will perform a read-only operation
  34. * on Redis or not.
  35. *
  36. * @param CommandInterface $command Command instance.
  37. *
  38. * @throws NotSupportedException
  39. *
  40. * @return bool
  41. */
  42. public function isReadOperation(CommandInterface $command)
  43. {
  44. if (isset($this->disallowed[$id = $command->getId()])) {
  45. throw new NotSupportedException(
  46. "The command '$id' is not allowed in replication mode."
  47. );
  48. }
  49. if (isset($this->readonly[$id])) {
  50. if (true === $readonly = $this->readonly[$id]) {
  51. return true;
  52. }
  53. return call_user_func($readonly, $command);
  54. }
  55. if (($eval = $id === 'EVAL') || $id === 'EVALSHA') {
  56. $sha1 = $eval ? sha1($command->getArgument(0)) : $command->getArgument(0);
  57. if (isset($this->readonlySHA1[$sha1])) {
  58. if (true === $readonly = $this->readonlySHA1[$sha1]) {
  59. return true;
  60. }
  61. return call_user_func($readonly, $command);
  62. }
  63. }
  64. return false;
  65. }
  66. /**
  67. * Returns if the specified command is not allowed for execution in a master
  68. * / slave replication context.
  69. *
  70. * @param CommandInterface $command Command instance.
  71. *
  72. * @return bool
  73. */
  74. public function isDisallowedOperation(CommandInterface $command)
  75. {
  76. return isset($this->disallowed[$command->getId()]);
  77. }
  78. /**
  79. * Checks if a SORT command is a readable operation by parsing the arguments
  80. * array of the specified commad instance.
  81. *
  82. * @param CommandInterface $command Command instance.
  83. *
  84. * @return bool
  85. */
  86. protected function isSortReadOnly(CommandInterface $command)
  87. {
  88. $arguments = $command->getArguments();
  89. $argc = count($arguments);
  90. if ($argc > 1) {
  91. for ($i = 1; $i < $argc; ++$i) {
  92. $argument = strtoupper($arguments[$i]);
  93. if ($argument === 'STORE') {
  94. return false;
  95. }
  96. }
  97. }
  98. return true;
  99. }
  100. /**
  101. * Checks if BITFIELD performs a read-only operation by looking for certain
  102. * SET and INCRYBY modifiers in the arguments array of the command.
  103. *
  104. * @param CommandInterface $command Command instance.
  105. *
  106. * @return bool
  107. */
  108. protected function isBitfieldReadOnly(CommandInterface $command)
  109. {
  110. $arguments = $command->getArguments();
  111. $argc = count($arguments);
  112. if ($argc >= 2) {
  113. for ($i = 1; $i < $argc; ++$i) {
  114. $argument = strtoupper($arguments[$i]);
  115. if ($argument === 'SET' || $argument === 'INCRBY') {
  116. return false;
  117. }
  118. }
  119. }
  120. return true;
  121. }
  122. /**
  123. * Checks if a GEORADIUS command is a readable operation by parsing the
  124. * arguments array of the specified commad instance.
  125. *
  126. * @param CommandInterface $command Command instance.
  127. *
  128. * @return bool
  129. */
  130. protected function isGeoradiusReadOnly(CommandInterface $command)
  131. {
  132. $arguments = $command->getArguments();
  133. $argc = count($arguments);
  134. $startIndex = $command->getId() === 'GEORADIUS' ? 5 : 4;
  135. if ($argc > $startIndex) {
  136. for ($i = $startIndex; $i < $argc; ++$i) {
  137. $argument = strtoupper($arguments[$i]);
  138. if ($argument === 'STORE' || $argument === 'STOREDIST') {
  139. return false;
  140. }
  141. }
  142. }
  143. return true;
  144. }
  145. /**
  146. * Marks a command as a read-only operation.
  147. *
  148. * When the behavior of a command can be decided only at runtime depending
  149. * on its arguments, a callable object can be provided to dynamically check
  150. * if the specified command performs a read or a write operation.
  151. *
  152. * @param string $commandID Command ID.
  153. * @param mixed $readonly A boolean value or a callable object.
  154. */
  155. public function setCommandReadOnly($commandID, $readonly = true)
  156. {
  157. $commandID = strtoupper($commandID);
  158. if ($readonly) {
  159. $this->readonly[$commandID] = $readonly;
  160. } else {
  161. unset($this->readonly[$commandID]);
  162. }
  163. }
  164. /**
  165. * Marks a Lua script for EVAL and EVALSHA as a read-only operation. When
  166. * the behaviour of a script can be decided only at runtime depending on
  167. * its arguments, a callable object can be provided to dynamically check
  168. * if the passed instance of EVAL or EVALSHA performs write operations or
  169. * not.
  170. *
  171. * @param string $script Body of the Lua script.
  172. * @param mixed $readonly A boolean value or a callable object.
  173. */
  174. public function setScriptReadOnly($script, $readonly = true)
  175. {
  176. $sha1 = sha1($script);
  177. if ($readonly) {
  178. $this->readonlySHA1[$sha1] = $readonly;
  179. } else {
  180. unset($this->readonlySHA1[$sha1]);
  181. }
  182. }
  183. /**
  184. * Returns the default list of disallowed commands.
  185. *
  186. * @return array
  187. */
  188. protected function getDisallowedOperations()
  189. {
  190. return array(
  191. 'SHUTDOWN' => true,
  192. 'INFO' => true,
  193. 'DBSIZE' => true,
  194. 'LASTSAVE' => true,
  195. 'CONFIG' => true,
  196. 'MONITOR' => true,
  197. 'SLAVEOF' => true,
  198. 'SAVE' => true,
  199. 'BGSAVE' => true,
  200. 'BGREWRITEAOF' => true,
  201. 'SLOWLOG' => true,
  202. );
  203. }
  204. /**
  205. * Returns the default list of commands performing read-only operations.
  206. *
  207. * @return array
  208. */
  209. protected function getReadOnlyOperations()
  210. {
  211. return array(
  212. 'EXISTS' => true,
  213. 'TYPE' => true,
  214. 'KEYS' => true,
  215. 'SCAN' => true,
  216. 'RANDOMKEY' => true,
  217. 'TTL' => true,
  218. 'GET' => true,
  219. 'MGET' => true,
  220. 'SUBSTR' => true,
  221. 'STRLEN' => true,
  222. 'GETRANGE' => true,
  223. 'GETBIT' => true,
  224. 'LLEN' => true,
  225. 'LRANGE' => true,
  226. 'LINDEX' => true,
  227. 'SCARD' => true,
  228. 'SISMEMBER' => true,
  229. 'SINTER' => true,
  230. 'SUNION' => true,
  231. 'SDIFF' => true,
  232. 'SMEMBERS' => true,
  233. 'SSCAN' => true,
  234. 'SRANDMEMBER' => true,
  235. 'ZRANGE' => true,
  236. 'ZREVRANGE' => true,
  237. 'ZRANGEBYSCORE' => true,
  238. 'ZREVRANGEBYSCORE' => true,
  239. 'ZCARD' => true,
  240. 'ZSCORE' => true,
  241. 'ZCOUNT' => true,
  242. 'ZRANK' => true,
  243. 'ZREVRANK' => true,
  244. 'ZSCAN' => true,
  245. 'ZLEXCOUNT' => true,
  246. 'ZRANGEBYLEX' => true,
  247. 'ZREVRANGEBYLEX' => true,
  248. 'HGET' => true,
  249. 'HMGET' => true,
  250. 'HEXISTS' => true,
  251. 'HLEN' => true,
  252. 'HKEYS' => true,
  253. 'HVALS' => true,
  254. 'HGETALL' => true,
  255. 'HSCAN' => true,
  256. 'HSTRLEN' => true,
  257. 'PING' => true,
  258. 'AUTH' => true,
  259. 'SELECT' => true,
  260. 'ECHO' => true,
  261. 'QUIT' => true,
  262. 'OBJECT' => true,
  263. 'BITCOUNT' => true,
  264. 'BITPOS' => true,
  265. 'TIME' => true,
  266. 'PFCOUNT' => true,
  267. 'SORT' => array($this, 'isSortReadOnly'),
  268. 'BITFIELD' => array($this, 'isBitfieldReadOnly'),
  269. 'GEOHASH' => true,
  270. 'GEOPOS' => true,
  271. 'GEODIST' => true,
  272. 'GEORADIUS' => array($this, 'isGeoradiusReadOnly'),
  273. 'GEORADIUSBYMEMBER' => array($this, 'isGeoradiusReadOnly'),
  274. );
  275. }
  276. }