ReplicationStrategy.php 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  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\NotSupportedException;
  12. use Predis\Command\CommandInterface;
  13. /**
  14. * Defines a strategy for master/reply 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 performs a read-only operation
  34. * against a key stored on Redis.
  35. *
  36. * @param CommandInterface $command Instance of Redis command.
  37. * @return Boolean
  38. */
  39. public function isReadOperation(CommandInterface $command)
  40. {
  41. if (isset($this->disallowed[$id = $command->getId()])) {
  42. throw new NotSupportedException("The command $id is not allowed in replication mode");
  43. }
  44. if (isset($this->readonly[$id])) {
  45. if (true === $readonly = $this->readonly[$id]) {
  46. return true;
  47. }
  48. return call_user_func($readonly, $command);
  49. }
  50. if (($eval = $id === 'EVAL') || $id === 'EVALSHA') {
  51. $sha1 = $eval ? sha1($command->getArgument(0)) : $command->getArgument(0);
  52. if (isset($this->readonlySHA1[$sha1])) {
  53. if (true === $readonly = $this->readonlySHA1[$sha1]) {
  54. return true;
  55. }
  56. return call_user_func($readonly, $command);
  57. }
  58. }
  59. return false;
  60. }
  61. /**
  62. * Returns if the specified command is disallowed in a master/slave
  63. * replication context.
  64. *
  65. * @param CommandInterface $command Instance of Redis command.
  66. * @return Boolean
  67. */
  68. public function isDisallowedOperation(CommandInterface $command)
  69. {
  70. return isset($this->disallowed[$command->getId()]);
  71. }
  72. /**
  73. * Checks if a SORT command is a readable operation by parsing the arguments
  74. * array of the specified commad instance.
  75. *
  76. * @param CommandInterface $command Instance of Redis command.
  77. * @return Boolean
  78. */
  79. protected function isSortReadOnly(CommandInterface $command)
  80. {
  81. $arguments = $command->getArguments();
  82. return ($c = count($arguments)) === 1 ? true : $arguments[$c - 2] !== 'STORE';
  83. }
  84. /**
  85. * Marks a command as a read-only operation. When the behaviour of a
  86. * command can be decided only at runtime depending on its arguments,
  87. * a callable object can be provided to dynamically check if the passed
  88. * instance of a command performs write operations or not.
  89. *
  90. * @param string $commandID ID of the command.
  91. * @param mixed $readonly A boolean or a callable object.
  92. */
  93. public function setCommandReadOnly($commandID, $readonly = true)
  94. {
  95. $commandID = strtoupper($commandID);
  96. if ($readonly) {
  97. $this->readonly[$commandID] = $readonly;
  98. } else {
  99. unset($this->readonly[$commandID]);
  100. }
  101. }
  102. /**
  103. * Marks a Lua script for EVAL and EVALSHA as a read-only operation. When
  104. * the behaviour of a script can be decided only at runtime depending on
  105. * its arguments, a callable object can be provided to dynamically check
  106. * if the passed instance of EVAL or EVALSHA performs write operations or
  107. * not.
  108. *
  109. * @param string $script Body of the Lua script.
  110. * @param mixed $readonly A boolean or a callable object.
  111. */
  112. public function setScriptReadOnly($script, $readonly = true)
  113. {
  114. $sha1 = sha1($script);
  115. if ($readonly) {
  116. $this->readonlySHA1[$sha1] = $readonly;
  117. } else {
  118. unset($this->readonlySHA1[$sha1]);
  119. }
  120. }
  121. /**
  122. * Returns the default list of disallowed commands.
  123. *
  124. * @return array
  125. */
  126. protected function getDisallowedOperations()
  127. {
  128. return array(
  129. 'SHUTDOWN' => true,
  130. 'INFO' => true,
  131. 'DBSIZE' => true,
  132. 'LASTSAVE' => true,
  133. 'CONFIG' => true,
  134. 'MONITOR' => true,
  135. 'SLAVEOF' => true,
  136. 'SAVE' => true,
  137. 'BGSAVE' => true,
  138. 'BGREWRITEAOF' => true,
  139. 'SLOWLOG' => true,
  140. );
  141. }
  142. /**
  143. * Returns the default list of commands performing read-only operations.
  144. *
  145. * @return array
  146. */
  147. protected function getReadOnlyOperations()
  148. {
  149. return array(
  150. 'EXISTS' => true,
  151. 'TYPE' => true,
  152. 'KEYS' => true,
  153. 'RANDOMKEY' => true,
  154. 'TTL' => true,
  155. 'GET' => true,
  156. 'MGET' => true,
  157. 'SUBSTR' => true,
  158. 'STRLEN' => true,
  159. 'GETRANGE' => true,
  160. 'GETBIT' => true,
  161. 'LLEN' => true,
  162. 'LRANGE' => true,
  163. 'LINDEX' => true,
  164. 'SCARD' => true,
  165. 'SISMEMBER' => true,
  166. 'SINTER' => true,
  167. 'SUNION' => true,
  168. 'SDIFF' => true,
  169. 'SMEMBERS' => true,
  170. 'SRANDMEMBER' => true,
  171. 'ZRANGE' => true,
  172. 'ZREVRANGE' => true,
  173. 'ZRANGEBYSCORE' => true,
  174. 'ZREVRANGEBYSCORE' => true,
  175. 'ZCARD' => true,
  176. 'ZSCORE' => true,
  177. 'ZCOUNT' => true,
  178. 'ZRANK' => true,
  179. 'ZREVRANK' => true,
  180. 'HGET' => true,
  181. 'HMGET' => true,
  182. 'HEXISTS' => true,
  183. 'HLEN' => true,
  184. 'HKEYS' => true,
  185. 'HVALS' => true,
  186. 'HGETALL' => true,
  187. 'PING' => true,
  188. 'AUTH' => true,
  189. 'SELECT' => true,
  190. 'ECHO' => true,
  191. 'QUIT' => true,
  192. 'OBJECT' => true,
  193. 'BITCOUNT' => true,
  194. 'TIME' => true,
  195. 'SORT' => array($this, 'isSortReadOnly'),
  196. );
  197. }
  198. }