MultiExecContextTest.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778
  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 PredisTestCase;
  12. use Predis\Client;
  13. use Predis\ResponseQueued;
  14. use Predis\ServerException;
  15. use Predis\Command\CommandInterface;
  16. /**
  17. * @group realm-transaction
  18. */
  19. class MultiExecContextTest extends PredisTestCase
  20. {
  21. /**
  22. * @group disconnected
  23. * @expectedException Predis\NotSupportedException
  24. * @expectedExceptionMessage The current profile does not support MULTI, EXEC and DISCARD
  25. */
  26. public function testThrowsExceptionOnUnsupportedMultiExecInProfile()
  27. {
  28. $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
  29. $client = new Client($connection, array('profile' => '1.2'));
  30. $tx = new MultiExecContext($client);
  31. }
  32. /**
  33. * @group disconnected
  34. * @expectedException Predis\NotSupportedException
  35. * @expectedExceptionMessage The current profile does not support WATCH and UNWATCH
  36. */
  37. public function testThrowsExceptionOnUnsupportedWatchUnwatchInProfile()
  38. {
  39. $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
  40. $client = new Client($connection, array('profile' => '2.0'));
  41. $tx = new MultiExecContext($client, array('options' => 'cas'));
  42. $tx->watch('foo');
  43. }
  44. /**
  45. * @group disconnected
  46. */
  47. public function testExecutionWithFluentInterface()
  48. {
  49. $commands = array();
  50. $expected = array('one', 'two', 'three');
  51. $callback = $this->getExecuteCallback($expected, $commands);
  52. $tx = $this->getMockedTransaction($callback);
  53. $this->assertSame($expected, $tx->echo('one')->echo('two')->echo('three')->execute());
  54. $this->assertSame(array('MULTI', 'ECHO', 'ECHO', 'ECHO', 'EXEC'), self::commandsToIDs($commands));
  55. }
  56. /**
  57. * @group disconnected
  58. */
  59. public function testExecutionWithCallable()
  60. {
  61. $commands = array();
  62. $expected = array('one', 'two', 'three');
  63. $callback = $this->getExecuteCallback($expected, $commands);
  64. $tx = $this->getMockedTransaction($callback);
  65. $replies = $tx->execute(function ($tx) {
  66. $tx->echo('one');
  67. $tx->echo('two');
  68. $tx->echo('three');
  69. });
  70. $this->assertSame($expected, $replies);
  71. $this->assertSame(array('MULTI', 'ECHO', 'ECHO', 'ECHO', 'EXEC'), self::commandsToIDs($commands));
  72. }
  73. /**
  74. * @group disconnected
  75. */
  76. public function testCannotMixExecutionWithFluentInterfaceAndCallable()
  77. {
  78. $exception = null;
  79. $commands = array();
  80. $callback = $this->getExecuteCallback(null, $commands);
  81. $tx = $this->getMockedTransaction($callback);
  82. $exception = null;
  83. try {
  84. $tx->echo('foo')->execute(function ($tx) {
  85. $tx->echo('bar');
  86. });
  87. } catch (\Exception $exception) {
  88. // NOOP
  89. }
  90. $this->assertInstanceOf('Predis\ClientException', $exception);
  91. $this->assertSame(array('MULTI', 'ECHO', 'DISCARD'), self::commandsToIDs($commands));
  92. }
  93. /**
  94. * @group disconnected
  95. */
  96. public function testEmptyTransactionDoesNotSendMultiExecCommands()
  97. {
  98. $commands = array();
  99. $callback = $this->getExecuteCallback(null, $commands);
  100. $tx = $this->getMockedTransaction($callback);
  101. $replies = $tx->execute(function ($tx) {
  102. // NOOP
  103. });
  104. $this->assertNull($replies);
  105. $this->assertSame(array(), self::commandsToIDs($commands));
  106. }
  107. /**
  108. * @group disconnected
  109. * @expectedException Predis\ClientException
  110. * @expectedExceptionMessage Cannot invoke 'execute' or 'exec' inside an active client transaction block
  111. */
  112. public function testThrowsExceptionOnExecInsideTransactionBlock()
  113. {
  114. $commands = array();
  115. $callback = $this->getExecuteCallback(null, $commands);
  116. $tx = $this->getMockedTransaction($callback);
  117. $replies = $tx->execute(function ($tx) {
  118. $tx->exec();
  119. });
  120. $this->assertNull($replies);
  121. $this->assertSame(array(), self::commandsToIDs($commands));
  122. }
  123. /**
  124. * @group disconnected
  125. */
  126. public function testEmptyTransactionIgnoresDiscard()
  127. {
  128. $commands = array();
  129. $callback = $this->getExecuteCallback(null, $commands);
  130. $tx = $this->getMockedTransaction($callback);
  131. $replies = $tx->execute(function ($tx) {
  132. $tx->discard();
  133. });
  134. $this->assertNull($replies);
  135. $this->assertSame(array(), self::commandsToIDs($commands));
  136. }
  137. /**
  138. * @group disconnected
  139. */
  140. public function testTransactionWithCommandsSendsDiscard()
  141. {
  142. $commands = array();
  143. $callback = $this->getExecuteCallback(null, $commands);
  144. $tx = $this->getMockedTransaction($callback);
  145. $replies = $tx->execute(function ($tx) {
  146. $tx->set('foo', 'bar');
  147. $tx->get('foo');
  148. $tx->discard();
  149. });
  150. $this->assertNull($replies);
  151. $this->assertSame(array('MULTI', 'SET', 'GET', 'DISCARD'), self::commandsToIDs($commands));
  152. }
  153. /**
  154. * @group disconnected
  155. */
  156. public function testSendMultiOnCommandsFollowingDiscard()
  157. {
  158. $commands = array();
  159. $expected = array('after DISCARD');
  160. $callback = $this->getExecuteCallback($expected, $commands);
  161. $tx = $this->getMockedTransaction($callback);
  162. $replies = $tx->execute(function ($tx) {
  163. $tx->echo('before DISCARD');
  164. $tx->discard();
  165. $tx->echo('after DISCARD');
  166. });
  167. $this->assertSame($replies, $expected);
  168. $this->assertSame(array('MULTI', 'ECHO', 'DISCARD', 'MULTI', 'ECHO', 'EXEC'), self::commandsToIDs($commands));
  169. }
  170. /**
  171. * @group disconnected
  172. * @expectedException Predis\ClientException
  173. */
  174. public function testThrowsExceptionOnWatchInsideMulti()
  175. {
  176. $callback = $this->getExecuteCallback();
  177. $tx = $this->getMockedTransaction($callback);
  178. $tx->echo('foobar')->watch('foo')->execute();
  179. }
  180. /**
  181. * @group disconnected
  182. */
  183. public function testUnwatchInsideMulti()
  184. {
  185. $commands = array();
  186. $expected = array('foobar', true);
  187. $callback = $this->getExecuteCallback($expected, $commands);
  188. $tx = $this->getMockedTransaction($callback);
  189. $replies = $tx->echo('foobar')->unwatch('foo')->execute();
  190. $this->assertSame($replies, $expected);
  191. $this->assertSame(array('MULTI', 'ECHO', 'UNWATCH', 'EXEC'), self::commandsToIDs($commands));
  192. }
  193. /**
  194. * @group disconnected
  195. */
  196. public function testAutomaticWatchInOptions()
  197. {
  198. $txCommands = $casCommands = array();
  199. $expected = array('bar', 'piyo');
  200. $options = array('watch' => array('foo', 'hoge'));
  201. $callback = $this->getExecuteCallback($expected, $txCommands, $casCommands);
  202. $tx = $this->getMockedTransaction($callback, $options);
  203. $replies = $tx->execute(function ($tx) {
  204. $tx->get('foo');
  205. $tx->get('hoge');
  206. });
  207. $this->assertSame($replies, $expected);
  208. $this->assertSame(array('WATCH'), self::commandsToIDs($casCommands));
  209. $this->assertSame(array('foo', 'hoge'), $casCommands[0]->getArguments());
  210. $this->assertSame(array('MULTI', 'GET', 'GET', 'EXEC'), self::commandsToIDs($txCommands));
  211. }
  212. /**
  213. * @group disconnected
  214. */
  215. public function testCheckAndSetWithFluentInterface()
  216. {
  217. $txCommands = $casCommands = array();
  218. $expected = array('bar', 'piyo');
  219. $options = array('cas' => true, 'watch' => array('foo', 'hoge'));
  220. $callback = $this->getExecuteCallback($expected, $txCommands, $casCommands);
  221. $tx = $this->getMockedTransaction($callback, $options);
  222. $tx->watch('foobar');
  223. $this->assertSame('DUMMY_REPLY', $tx->get('foo'));
  224. $this->assertSame('DUMMY_REPLY', $tx->get('hoge'));
  225. $replies = $tx->multi()
  226. ->get('foo')
  227. ->get('hoge')
  228. ->execute();
  229. $this->assertSame($replies, $expected);
  230. $this->assertSame(array('WATCH', 'WATCH', 'GET', 'GET'), self::commandsToIDs($casCommands));
  231. $this->assertSame(array('MULTI', 'GET', 'GET', 'EXEC'), self::commandsToIDs($txCommands));
  232. }
  233. /**
  234. * @group disconnected
  235. */
  236. public function testCheckAndSetWithBlock()
  237. {
  238. $txCommands = $casCommands = array();
  239. $expected = array('bar', 'piyo');
  240. $options = array('cas' => true, 'watch' => array('foo', 'hoge'));
  241. $callback = $this->getExecuteCallback($expected, $txCommands, $casCommands);
  242. $tx = $this->getMockedTransaction($callback, $options);
  243. $test = $this;
  244. $replies = $tx->execute(function ($tx) use ($test) {
  245. $tx->watch('foobar');
  246. $reply1 = $tx->get('foo');
  247. $reply2 = $tx->get('hoge');
  248. $test->assertSame('DUMMY_REPLY', $reply1);
  249. $test->assertSame('DUMMY_REPLY', $reply2);
  250. $tx->multi();
  251. $tx->get('foo');
  252. $tx->get('hoge');
  253. });
  254. $this->assertSame($replies, $expected);
  255. $this->assertSame(array('WATCH', 'WATCH', 'GET', 'GET'), self::commandsToIDs($casCommands));
  256. $this->assertSame(array('MULTI', 'GET', 'GET', 'EXEC'), self::commandsToIDs($txCommands));
  257. }
  258. /**
  259. * @group disconnected
  260. */
  261. public function testCheckAndSetWithEmptyBlock()
  262. {
  263. $txCommands = $casCommands = array();
  264. $options = array('cas' => true);
  265. $callback = $this->getExecuteCallback(array(), $txCommands, $casCommands);
  266. $tx = $this->getMockedTransaction($callback, $options);
  267. $tx->execute(function ($tx) {
  268. $tx->multi();
  269. });
  270. $this->assertSame(array(), self::commandsToIDs($casCommands));
  271. $this->assertSame(array(), self::commandsToIDs($txCommands));
  272. }
  273. /**
  274. * @group disconnected
  275. */
  276. public function testCheckAndSetWithoutExec()
  277. {
  278. $txCommands = $casCommands = array();
  279. $options = array('cas' => true);
  280. $callback = $this->getExecuteCallback(array(), $txCommands, $casCommands);
  281. $tx = $this->getMockedTransaction($callback, $options);
  282. $tx->execute(function ($tx) {
  283. $bar = $tx->get('foo');
  284. $tx->set('hoge', 'piyo');
  285. });
  286. $this->assertSame(array('GET', 'SET'), self::commandsToIDs($casCommands));
  287. $this->assertSame(array(), self::commandsToIDs($txCommands));
  288. }
  289. /**
  290. * @group disconnected
  291. * @expectedException InvalidArgumentException
  292. * @expectedExceptionMessage Automatic retries can be used only when a transaction block is provided
  293. */
  294. public function testThrowsExceptionOnAutomaticRetriesWithFluentInterface()
  295. {
  296. $options = array('retry' => 1);
  297. $callback = $this->getExecuteCallback();
  298. $tx = $this->getMockedTransaction($callback, $options);
  299. $tx->echo('message')->execute();
  300. }
  301. /**
  302. * @group disconnected
  303. */
  304. public function testAutomaticRetryOnServerSideTransactionAbort()
  305. {
  306. $casCommands = $txCommands = array();
  307. $expected = array('bar');
  308. $options = array('watch' => array('foo', 'bar'), 'retry' => ($attempts = 2) + 1);
  309. $sentinel = $this->getMock('stdClass', array('signal'));
  310. $sentinel->expects($this->exactly($attempts))->method('signal');
  311. $callback = $this->getExecuteCallback($expected, $txCommands, $casCommands);
  312. $tx = $this->getMockedTransaction($callback, $options);
  313. $replies = $tx->execute(function ($tx) use ($sentinel, &$attempts) {
  314. $tx->get('foo');
  315. if ($attempts > 0) {
  316. $attempts -= 1;
  317. $sentinel->signal();
  318. $tx->echo('!!ABORT!!');
  319. }
  320. });
  321. $this->assertSame($replies, $expected);
  322. $this->assertSame(array('WATCH'), self::commandsToIDs($casCommands));
  323. $this->assertSame(array('foo', 'bar'), $casCommands[0]->getArguments());
  324. $this->assertSame(array('MULTI', 'GET', 'EXEC'), self::commandsToIDs($txCommands));
  325. }
  326. /**
  327. * @group disconnected
  328. * @expectedException Predis\Transaction\AbortedMultiExecException
  329. */
  330. public function testThrowsExceptionOnServerSideTransactionAbort()
  331. {
  332. $callback = $this->getExecuteCallback();
  333. $tx = $this->getMockedTransaction($callback);
  334. $replies = $tx->execute(function ($tx) {
  335. $tx->echo('!!ABORT!!');
  336. });
  337. }
  338. /**
  339. * @group disconnected
  340. */
  341. public function testHandlesStandardExceptionsInBlock()
  342. {
  343. $exception = null;
  344. $commands = array();
  345. $expected = array('foobar', true);
  346. $callback = $this->getExecuteCallback($expected, $commands);
  347. $tx = $this->getMockedTransaction($callback);
  348. $replies = null;
  349. try {
  350. $replies = $tx->execute(function ($tx) {
  351. $tx->set('foo', 'bar');
  352. $tx->get('foo');
  353. throw new \RuntimeException('TEST');
  354. });
  355. } catch (\Exception $exception) {
  356. // NOOP
  357. }
  358. $this->assertNull($replies, $expected);
  359. $this->assertSame(array('MULTI', 'SET', 'GET', 'DISCARD'), self::commandsToIDs($commands));
  360. }
  361. /**
  362. * @group disconnected
  363. */
  364. public function testHandlesServerExceptionsInBlock()
  365. {
  366. $commands = array();
  367. $expected = array('foobar', true);
  368. $callback = $this->getExecuteCallback($expected, $commands);
  369. $tx = $this->getMockedTransaction($callback);
  370. $replies = null;
  371. try {
  372. $replies = $tx->execute(function ($tx) {
  373. $tx->set('foo', 'bar');
  374. $tx->echo('ERR Invalid operation');
  375. $tx->get('foo');
  376. });
  377. } catch (ServerException $exception) {
  378. $tx->discard();
  379. }
  380. $this->assertNull($replies);
  381. $this->assertSame(array('MULTI', 'SET', 'ECHO', 'DISCARD'), self::commandsToIDs($commands));
  382. }
  383. // ******************************************************************** //
  384. // ---- INTEGRATION TESTS --------------------------------------------- //
  385. // ******************************************************************** //
  386. /**
  387. * @group connected
  388. */
  389. public function testIntegrationHandlesStandardExceptionsInBlock()
  390. {
  391. $exception = null;
  392. $client = $this->getClient();
  393. try {
  394. $client->multiExec(function ($tx) {
  395. $tx->set('foo', 'bar');
  396. throw new \RuntimeException("TEST");
  397. });
  398. } catch (\Exception $exception) {
  399. // NOOP
  400. }
  401. $this->assertInstanceOf('RuntimeException', $exception);
  402. $this->assertFalse($client->exists('foo'));
  403. }
  404. /**
  405. * @group connected
  406. */
  407. public function testIntegrationThrowsExceptionOnRedisErrorInBlock()
  408. {
  409. $exception = null;
  410. $client = $this->getClient();
  411. $value = (string) rand();
  412. try {
  413. $client->multiExec(function ($tx) use ($value) {
  414. $tx->set('foo', 'bar');
  415. $tx->lpush('foo', 'bar');
  416. $tx->set('foo', $value);
  417. });
  418. } catch (ServerException $exception) {
  419. // NOOP
  420. }
  421. $this->assertInstanceOf('Predis\ResponseErrorInterface', $exception);
  422. $this->assertSame($value, $client->get('foo'));
  423. }
  424. /**
  425. * @group connected
  426. */
  427. public function testIntegrationReturnsErrorObjectOnRedisErrorInBlock()
  428. {
  429. $client = $this->getClient(array(), array('exceptions' => false));
  430. $replies = $client->multiExec(function ($tx) {
  431. $tx->set('foo', 'bar');
  432. $tx->lpush('foo', 'bar');
  433. $tx->echo('foobar');
  434. });
  435. $this->assertTrue($replies[0]);
  436. $this->assertInstanceOf('Predis\ResponseErrorInterface', $replies[1]);
  437. $this->assertSame('foobar', $replies[2]);
  438. }
  439. /**
  440. * @group connected
  441. */
  442. public function testIntegrationSendMultiOnCommandsAfterDiscard()
  443. {
  444. $client = $this->getClient();
  445. $replies = $client->multiExec(function ($tx) {
  446. $tx->set('foo', 'bar');
  447. $tx->discard();
  448. $tx->set('hoge', 'piyo');
  449. });
  450. $this->assertSame(1, count($replies));
  451. $this->assertFalse($client->exists('foo'));
  452. $this->assertTrue($client->exists('hoge'));
  453. }
  454. /**
  455. * @group connected
  456. */
  457. public function testIntegrationWritesOnWatchedKeysAbortTransaction()
  458. {
  459. $exception = null;
  460. $client1 = $this->getClient();
  461. $client2 = $this->getClient();
  462. try {
  463. $client1->multiExec(array('watch' => 'sentinel'), function ($tx) use ($client2) {
  464. $tx->set('sentinel', 'client1');
  465. $tx->get('sentinel');
  466. $client2->set('sentinel', 'client2');
  467. });
  468. } catch (AbortedMultiExecException $exception) {
  469. // NOOP
  470. }
  471. $this->assertInstanceOf('Predis\Transaction\AbortedMultiExecException', $exception);
  472. $this->assertSame('client2', $client1->get('sentinel'));
  473. }
  474. /**
  475. * @group connected
  476. */
  477. public function testIntegrationCheckAndSetWithDiscardAndRetry()
  478. {
  479. $client = $this->getClient();
  480. $client->set('foo', 'bar');
  481. $options = array('watch' => 'foo', 'cas' => true);
  482. $replies = $client->multiExec($options, function ($tx) {
  483. $tx->watch('foobar');
  484. $foo = $tx->get('foo');
  485. $tx->multi();
  486. $tx->set('foobar', $foo);
  487. $tx->discard();
  488. $tx->mget('foo', 'foobar');
  489. });
  490. $this->assertInternalType('array', $replies);
  491. $this->assertSame(array(array('bar', null)), $replies);
  492. $hijack = true;
  493. $client2 = $this->getClient();
  494. $client->set('foo', 'bar');
  495. $options = array('watch' => 'foo', 'cas' => true, 'retry' => 1);
  496. $replies = $client->multiExec($options, function ($tx) use ($client2, &$hijack) {
  497. $foo = $tx->get('foo');
  498. $tx->multi();
  499. $tx->set('foobar', $foo);
  500. $tx->discard();
  501. if ($hijack) {
  502. $hijack = false;
  503. $client2->set('foo', 'hijacked!');
  504. }
  505. $tx->mget('foo', 'foobar');
  506. });
  507. $this->assertInternalType('array', $replies);
  508. $this->assertSame(array(array('hijacked!', null)), $replies);
  509. }
  510. // ******************************************************************** //
  511. // ---- HELPER METHODS ------------------------------------------------ //
  512. // ******************************************************************** //
  513. /**
  514. * Returns a mocked instance of Predis\Connection\SingleConnectionInterface
  515. * usingthe specified callback to return values from executeCommand().
  516. *
  517. * @param \Closure $executeCallback
  518. * @return \Predis\Connection\SingleConnectionInterface
  519. */
  520. protected function getMockedConnection($executeCallback)
  521. {
  522. $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
  523. $connection->expects($this->any())
  524. ->method('executeCommand')
  525. ->will($this->returnCallback($executeCallback));
  526. return $connection;
  527. }
  528. /**
  529. * Returns a mocked instance of Predis\Transaction\MultiExecContext using
  530. * the specified callback to return values from the executeCommand method
  531. * of the underlying connection.
  532. *
  533. * @param \Closure $executeCallback
  534. * @param array $options
  535. * @return MultiExecContext
  536. */
  537. protected function getMockedTransaction($executeCallback, $options = array())
  538. {
  539. $connection = $this->getMockedConnection($executeCallback);
  540. $client = new Client($connection);
  541. $transaction = new MultiExecContext($client, $options);
  542. return $transaction;
  543. }
  544. /**
  545. * Returns a callback that emulates a server-side MULTI/EXEC transaction context.
  546. *
  547. * @param array $expected Expected responses.
  548. * @param array $commands Reference to an array storing the whole flow of commands.
  549. * @param array $cas Check and set operations performed by the transaction.
  550. * @return \Closure
  551. */
  552. protected function getExecuteCallback($expected = array(), &$commands = array(), &$cas = array())
  553. {
  554. $multi = $watch = $abort = false;
  555. return function (CommandInterface $command) use (&$expected, &$commands, &$cas, &$multi, &$watch, &$abort) {
  556. $cmd = $command->getId();
  557. if ($multi || $cmd === 'MULTI') {
  558. $commands[] = $command;
  559. } else {
  560. $cas[] = $command;
  561. }
  562. switch ($cmd) {
  563. case 'WATCH':
  564. if ($multi) {
  565. throw new ServerException("ERR $cmd inside MULTI is not allowed");
  566. }
  567. return $watch = true;
  568. case 'MULTI':
  569. if ($multi) {
  570. throw new ServerException("ERR MULTI calls can not be nested");
  571. }
  572. return $multi = true;
  573. case 'EXEC':
  574. if (!$multi) {
  575. throw new ServerException("ERR $cmd without MULTI");
  576. }
  577. $watch = $multi = false;
  578. if ($abort) {
  579. $commands = $cas = array();
  580. $abort = false;
  581. return null;
  582. }
  583. return $expected;
  584. case 'DISCARD':
  585. if (!$multi) {
  586. throw new ServerException("ERR $cmd without MULTI");
  587. }
  588. $watch = $multi = false;
  589. return true;
  590. case 'ECHO':
  591. @list($trigger) = $command->getArguments();
  592. if (strpos($trigger, 'ERR ') === 0) {
  593. throw new ServerException($trigger);
  594. }
  595. if ($trigger === '!!ABORT!!' && $multi) {
  596. $abort = true;
  597. }
  598. return new ResponseQueued();
  599. case 'UNWATCH':
  600. $watch = false;
  601. default:
  602. return $multi ? new ResponseQueued() : 'DUMMY_REPLY';
  603. }
  604. };
  605. }
  606. /**
  607. * Converts an array of instances of Predis\Command\CommandInterface and
  608. * returns an array containing their IDs.
  609. *
  610. * @param array $commands List of commands instances.
  611. * @return array
  612. */
  613. protected static function commandsToIDs($commands)
  614. {
  615. return array_map(function ($cmd) { return $cmd->getId(); }, $commands);
  616. }
  617. /**
  618. * Returns a client instance connected to the specified Redis
  619. * server instance to perform integration tests.
  620. *
  621. * @param array Additional connection parameters.
  622. * @param array Additional client options.
  623. * @return Client client instance.
  624. */
  625. protected function getClient(array $parameters = array(), array $options = array())
  626. {
  627. return $this->createClient($parameters, $options);
  628. }
  629. }