PageRenderTime 50ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/foody.blogtamsudev.vn/vendor/predis/predis/src/Transaction/MultiExec.php

https://gitlab.com/ntphuc/BackendFeedy
PHP | 461 lines | 254 code | 70 blank | 137 comment | 42 complexity | 4dec1fdf55c3ef9118e3ca3a4b517a45 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\Transaction;
  11. use Predis\ClientContextInterface;
  12. use Predis\ClientException;
  13. use Predis\ClientInterface;
  14. use Predis\Command\CommandInterface;
  15. use Predis\CommunicationException;
  16. use Predis\Connection\AggregateConnectionInterface;
  17. use Predis\NotSupportedException;
  18. use Predis\Protocol\ProtocolException;
  19. use Predis\Response\ErrorInterface as ErrorResponseInterface;
  20. use Predis\Response\ServerException;
  21. use Predis\Response\Status as StatusResponse;
  22. /**
  23. * Client-side abstraction of a Redis transaction based on MULTI / EXEC.
  24. *
  25. * {@inheritdoc}
  26. *
  27. * @author Daniele Alessandri <suppakilla@gmail.com>
  28. */
  29. class MultiExec implements ClientContextInterface
  30. {
  31. private $state;
  32. protected $client;
  33. protected $commands;
  34. protected $exceptions = true;
  35. protected $attempts = 0;
  36. protected $watchKeys = array();
  37. protected $modeCAS = false;
  38. /**
  39. * @param ClientInterface $client Client instance used by the transaction.
  40. * @param array $options Initialization options.
  41. */
  42. public function __construct(ClientInterface $client, array $options = null)
  43. {
  44. $this->assertClient($client);
  45. $this->client = $client;
  46. $this->state = new MultiExecState();
  47. $this->configure($client, $options ?: array());
  48. $this->reset();
  49. }
  50. /**
  51. * Checks if the passed client instance satisfies the required conditions
  52. * needed to initialize the transaction object.
  53. *
  54. * @param ClientInterface $client Client instance used by the transaction object.
  55. *
  56. * @throws NotSupportedException
  57. */
  58. private function assertClient(ClientInterface $client)
  59. {
  60. if ($client->getConnection() instanceof AggregateConnectionInterface) {
  61. throw new NotSupportedException(
  62. 'Cannot initialize a MULTI/EXEC transaction over aggregate connections.'
  63. );
  64. }
  65. if (!$client->getProfile()->supportsCommands(array('MULTI', 'EXEC', 'DISCARD'))) {
  66. throw new NotSupportedException(
  67. 'The current profile does not support MULTI, EXEC and DISCARD.'
  68. );
  69. }
  70. }
  71. /**
  72. * Configures the transaction using the provided options.
  73. *
  74. * @param ClientInterface $client Underlying client instance.
  75. * @param array $options Array of options for the transaction.
  76. **/
  77. protected function configure(ClientInterface $client, array $options)
  78. {
  79. if (isset($options['exceptions'])) {
  80. $this->exceptions = (bool) $options['exceptions'];
  81. } else {
  82. $this->exceptions = $client->getOptions()->exceptions;
  83. }
  84. if (isset($options['cas'])) {
  85. $this->modeCAS = (bool) $options['cas'];
  86. }
  87. if (isset($options['watch']) && $keys = $options['watch']) {
  88. $this->watchKeys = $keys;
  89. }
  90. if (isset($options['retry'])) {
  91. $this->attempts = (int) $options['retry'];
  92. }
  93. }
  94. /**
  95. * Resets the state of the transaction.
  96. */
  97. protected function reset()
  98. {
  99. $this->state->reset();
  100. $this->commands = new \SplQueue();
  101. }
  102. /**
  103. * Initializes the transaction context.
  104. */
  105. protected function initialize()
  106. {
  107. if ($this->state->isInitialized()) {
  108. return;
  109. }
  110. if ($this->modeCAS) {
  111. $this->state->flag(MultiExecState::CAS);
  112. }
  113. if ($this->watchKeys) {
  114. $this->watch($this->watchKeys);
  115. }
  116. $cas = $this->state->isCAS();
  117. $discarded = $this->state->isDiscarded();
  118. if (!$cas || ($cas && $discarded)) {
  119. $this->call('MULTI');
  120. if ($discarded) {
  121. $this->state->unflag(MultiExecState::CAS);
  122. }
  123. }
  124. $this->state->unflag(MultiExecState::DISCARDED);
  125. $this->state->flag(MultiExecState::INITIALIZED);
  126. }
  127. /**
  128. * Dynamically invokes a Redis command with the specified arguments.
  129. *
  130. * @param string $method Command ID.
  131. * @param array $arguments Arguments for the command.
  132. *
  133. * @return mixed
  134. */
  135. public function __call($method, $arguments)
  136. {
  137. return $this->executeCommand(
  138. $this->client->createCommand($method, $arguments)
  139. );
  140. }
  141. /**
  142. * Executes a Redis command bypassing the transaction logic.
  143. *
  144. * @param string $commandID Command ID.
  145. * @param array $arguments Arguments for the command.
  146. *
  147. * @throws ServerException
  148. *
  149. * @return mixed
  150. */
  151. protected function call($commandID, array $arguments = array())
  152. {
  153. $response = $this->client->executeCommand(
  154. $this->client->createCommand($commandID, $arguments)
  155. );
  156. if ($response instanceof ErrorResponseInterface) {
  157. throw new ServerException($response->getMessage());
  158. }
  159. return $response;
  160. }
  161. /**
  162. * Executes the specified Redis command.
  163. *
  164. * @param CommandInterface $command Command instance.
  165. *
  166. * @throws AbortedMultiExecException
  167. * @throws CommunicationException
  168. *
  169. * @return $this|mixed
  170. */
  171. public function executeCommand(CommandInterface $command)
  172. {
  173. $this->initialize();
  174. if ($this->state->isCAS()) {
  175. return $this->client->executeCommand($command);
  176. }
  177. $response = $this->client->getConnection()->executeCommand($command);
  178. if ($response instanceof StatusResponse && $response == 'QUEUED') {
  179. $this->commands->enqueue($command);
  180. } elseif ($response instanceof ErrorResponseInterface) {
  181. throw new AbortedMultiExecException($this, $response->getMessage());
  182. } else {
  183. $this->onProtocolError('The server did not return a +QUEUED status response.');
  184. }
  185. return $this;
  186. }
  187. /**
  188. * Executes WATCH against one or more keys.
  189. *
  190. * @param string|array $keys One or more keys.
  191. *
  192. * @throws NotSupportedException
  193. * @throws ClientException
  194. *
  195. * @return mixed
  196. */
  197. public function watch($keys)
  198. {
  199. if (!$this->client->getProfile()->supportsCommand('WATCH')) {
  200. throw new NotSupportedException('WATCH is not supported by the current profile.');
  201. }
  202. if ($this->state->isWatchAllowed()) {
  203. throw new ClientException('Sending WATCH after MULTI is not allowed.');
  204. }
  205. $response = $this->call('WATCH', is_array($keys) ? $keys : array($keys));
  206. $this->state->flag(MultiExecState::WATCH);
  207. return $response;
  208. }
  209. /**
  210. * Finalizes the transaction by executing MULTI on the server.
  211. *
  212. * @return MultiExec
  213. */
  214. public function multi()
  215. {
  216. if ($this->state->check(MultiExecState::INITIALIZED | MultiExecState::CAS)) {
  217. $this->state->unflag(MultiExecState::CAS);
  218. $this->call('MULTI');
  219. } else {
  220. $this->initialize();
  221. }
  222. return $this;
  223. }
  224. /**
  225. * Executes UNWATCH.
  226. *
  227. * @throws NotSupportedException
  228. *
  229. * @return MultiExec
  230. */
  231. public function unwatch()
  232. {
  233. if (!$this->client->getProfile()->supportsCommand('UNWATCH')) {
  234. throw new NotSupportedException(
  235. 'UNWATCH is not supported by the current profile.'
  236. );
  237. }
  238. $this->state->unflag(MultiExecState::WATCH);
  239. $this->__call('UNWATCH', array());
  240. return $this;
  241. }
  242. /**
  243. * Resets the transaction by UNWATCH-ing the keys that are being WATCHed and
  244. * DISCARD-ing pending commands that have been already sent to the server.
  245. *
  246. * @return MultiExec
  247. */
  248. public function discard()
  249. {
  250. if ($this->state->isInitialized()) {
  251. $this->call($this->state->isCAS() ? 'UNWATCH' : 'DISCARD');
  252. $this->reset();
  253. $this->state->flag(MultiExecState::DISCARDED);
  254. }
  255. return $this;
  256. }
  257. /**
  258. * Executes the whole transaction.
  259. *
  260. * @return mixed
  261. */
  262. public function exec()
  263. {
  264. return $this->execute();
  265. }
  266. /**
  267. * Checks the state of the transaction before execution.
  268. *
  269. * @param mixed $callable Callback for execution.
  270. *
  271. * @throws \InvalidArgumentException
  272. * @throws ClientException
  273. */
  274. private function checkBeforeExecution($callable)
  275. {
  276. if ($this->state->isExecuting()) {
  277. throw new ClientException(
  278. 'Cannot invoke "execute" or "exec" inside an active transaction context.'
  279. );
  280. }
  281. if ($callable) {
  282. if (!is_callable($callable)) {
  283. throw new \InvalidArgumentException('The argument must be a callable object.');
  284. }
  285. if (!$this->commands->isEmpty()) {
  286. $this->discard();
  287. throw new ClientException(
  288. 'Cannot execute a transaction block after using fluent interface.'
  289. );
  290. }
  291. } elseif ($this->attempts) {
  292. $this->discard();
  293. throw new ClientException(
  294. 'Automatic retries are supported only when a callable block is provided.'
  295. );
  296. }
  297. }
  298. /**
  299. * Handles the actual execution of the whole transaction.
  300. *
  301. * @param mixed $callable Optional callback for execution.
  302. *
  303. * @throws CommunicationException
  304. * @throws AbortedMultiExecException
  305. * @throws ServerException
  306. *
  307. * @return array
  308. */
  309. public function execute($callable = null)
  310. {
  311. $this->checkBeforeExecution($callable);
  312. $execResponse = null;
  313. $attempts = $this->attempts;
  314. do {
  315. if ($callable) {
  316. $this->executeTransactionBlock($callable);
  317. }
  318. if ($this->commands->isEmpty()) {
  319. if ($this->state->isWatching()) {
  320. $this->discard();
  321. }
  322. return;
  323. }
  324. $execResponse = $this->call('EXEC');
  325. if ($execResponse === null) {
  326. if ($attempts === 0) {
  327. throw new AbortedMultiExecException(
  328. $this, 'The current transaction has been aborted by the server.'
  329. );
  330. }
  331. $this->reset();
  332. continue;
  333. }
  334. break;
  335. } while ($attempts-- > 0);
  336. $response = array();
  337. $commands = $this->commands;
  338. $size = count($execResponse);
  339. if ($size !== count($commands)) {
  340. $this->onProtocolError('EXEC returned an unexpected number of response items.');
  341. }
  342. for ($i = 0; $i < $size; ++$i) {
  343. $cmdResponse = $execResponse[$i];
  344. if ($cmdResponse instanceof ErrorResponseInterface && $this->exceptions) {
  345. throw new ServerException($cmdResponse->getMessage());
  346. }
  347. $response[$i] = $commands->dequeue()->parseResponse($cmdResponse);
  348. }
  349. return $response;
  350. }
  351. /**
  352. * Passes the current transaction object to a callable block for execution.
  353. *
  354. * @param mixed $callable Callback.
  355. *
  356. * @throws CommunicationException
  357. * @throws ServerException
  358. */
  359. protected function executeTransactionBlock($callable)
  360. {
  361. $exception = null;
  362. $this->state->flag(MultiExecState::INSIDEBLOCK);
  363. try {
  364. call_user_func($callable, $this);
  365. } catch (CommunicationException $exception) {
  366. // NOOP
  367. } catch (ServerException $exception) {
  368. // NOOP
  369. } catch (\Exception $exception) {
  370. $this->discard();
  371. }
  372. $this->state->unflag(MultiExecState::INSIDEBLOCK);
  373. if ($exception) {
  374. throw $exception;
  375. }
  376. }
  377. /**
  378. * Helper method for protocol errors encountered inside the transaction.
  379. *
  380. * @param string $message Error message.
  381. */
  382. private function onProtocolError($message)
  383. {
  384. // Since a MULTI/EXEC block cannot be initialized when using aggregate
  385. // connections we can safely assume that Predis\Client::getConnection()
  386. // will return a Predis\Connection\NodeConnectionInterface instance.
  387. CommunicationException::handle(new ProtocolException(
  388. $this->client->getConnection(), $message
  389. ));
  390. }
  391. }