ReplicationStrategy.php 6.5 KB

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