MultiExecContextTest.php 24 KB

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