ReplicationStrategy.php 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  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. return ($c = count($arguments)) === 1 ? true : $arguments[$c - 2] !== 'STORE';
  90. }
  91. /**
  92. * Checks if BITFIELD performs a read-only operation by looking for certain
  93. * SET and INCRYBY modifiers in the arguments array of the command.
  94. *
  95. * @param CommandInterface $command Command instance.
  96. *
  97. * @return bool
  98. */
  99. protected function isBitfieldReadOnly(CommandInterface $command)
  100. {
  101. $arguments = $command->getArguments();
  102. $argc = count($arguments);
  103. if ($argc >= 2) {
  104. for ($i = 1; $i < $argc; $i++) {
  105. $argument = strtoupper($arguments[$i]);
  106. if ($argument === 'SET' || $argument === 'INCRBY') {
  107. return false;
  108. }
  109. }
  110. }
  111. return true;
  112. }
  113. /**
  114. * Marks a command as a read-only operation.
  115. *
  116. * When the behavior of a command can be decided only at runtime depending
  117. * on its arguments, a callable object can be provided to dynamically check
  118. * if the specified command performs a read or a write operation.
  119. *
  120. * @param string $commandID Command ID.
  121. * @param mixed $readonly A boolean value or a callable object.
  122. */
  123. public function setCommandReadOnly($commandID, $readonly = true)
  124. {
  125. $commandID = strtoupper($commandID);
  126. if ($readonly) {
  127. $this->readonly[$commandID] = $readonly;
  128. } else {
  129. unset($this->readonly[$commandID]);
  130. }
  131. }
  132. /**
  133. * Marks a Lua script for EVAL and EVALSHA as a read-only operation. When
  134. * the behaviour of a script can be decided only at runtime depending on
  135. * its arguments, a callable object can be provided to dynamically check
  136. * if the passed instance of EVAL or EVALSHA performs write operations or
  137. * not.
  138. *
  139. * @param string $script Body of the Lua script.
  140. * @param mixed $readonly A boolean value or a callable object.
  141. */
  142. public function setScriptReadOnly($script, $readonly = true)
  143. {
  144. $sha1 = sha1($script);
  145. if ($readonly) {
  146. $this->readonlySHA1[$sha1] = $readonly;
  147. } else {
  148. unset($this->readonlySHA1[$sha1]);
  149. }
  150. }
  151. /**
  152. * Returns the default list of disallowed commands.
  153. *
  154. * @return array
  155. */
  156. protected function getDisallowedOperations()
  157. {
  158. return array(
  159. 'SHUTDOWN' => true,
  160. 'INFO' => true,
  161. 'DBSIZE' => true,
  162. 'LASTSAVE' => true,
  163. 'CONFIG' => true,
  164. 'MONITOR' => true,
  165. 'SLAVEOF' => true,
  166. 'SAVE' => true,
  167. 'BGSAVE' => true,
  168. 'BGREWRITEAOF' => true,
  169. 'SLOWLOG' => true,
  170. );
  171. }
  172. /**
  173. * Returns the default list of commands performing read-only operations.
  174. *
  175. * @return array
  176. */
  177. protected function getReadOnlyOperations()
  178. {
  179. return array(
  180. 'EXISTS' => true,
  181. 'TYPE' => true,
  182. 'KEYS' => true,
  183. 'SCAN' => true,
  184. 'RANDOMKEY' => true,
  185. 'TTL' => true,
  186. 'GET' => true,
  187. 'MGET' => true,
  188. 'SUBSTR' => true,
  189. 'STRLEN' => true,
  190. 'GETRANGE' => true,
  191. 'GETBIT' => true,
  192. 'LLEN' => true,
  193. 'LRANGE' => true,
  194. 'LINDEX' => true,
  195. 'SCARD' => true,
  196. 'SISMEMBER' => true,
  197. 'SINTER' => true,
  198. 'SUNION' => true,
  199. 'SDIFF' => true,
  200. 'SMEMBERS' => true,
  201. 'SSCAN' => true,
  202. 'SRANDMEMBER' => true,
  203. 'ZRANGE' => true,
  204. 'ZREVRANGE' => true,
  205. 'ZRANGEBYSCORE' => true,
  206. 'ZREVRANGEBYSCORE' => true,
  207. 'ZCARD' => true,
  208. 'ZSCORE' => true,
  209. 'ZCOUNT' => true,
  210. 'ZRANK' => true,
  211. 'ZREVRANK' => true,
  212. 'ZSCAN' => true,
  213. 'ZLEXCOUNT' => true,
  214. 'ZRANGEBYLEX' => true,
  215. 'ZREVRANGEBYLEX' => true,
  216. 'HGET' => true,
  217. 'HMGET' => true,
  218. 'HEXISTS' => true,
  219. 'HLEN' => true,
  220. 'HKEYS' => true,
  221. 'HVALS' => true,
  222. 'HGETALL' => true,
  223. 'HSCAN' => true,
  224. 'HSTRLEN' => true,
  225. 'PING' => true,
  226. 'AUTH' => true,
  227. 'SELECT' => true,
  228. 'ECHO' => true,
  229. 'QUIT' => true,
  230. 'OBJECT' => true,
  231. 'BITCOUNT' => true,
  232. 'BITPOS' => true,
  233. 'TIME' => true,
  234. 'PFCOUNT' => true,
  235. 'SORT' => array($this, 'isSortReadOnly'),
  236. 'BITFIELD' => array($this, 'isBitfieldReadOnly'),
  237. 'GEOHASH' => true,
  238. 'GEOPOS' => true,
  239. );
  240. }
  241. }