MultiExecTest.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783
  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\Command\CommandInterface;
  14. use Predis\Response;
  15. /**
  16. * @group realm-transaction
  17. */
  18. class MultiExecTest extends StandardTestCase
  19. {
  20. /**
  21. * @group disconnected
  22. * @expectedException Predis\NotSupportedException
  23. * @expectedExceptionMessage The current profile does not support MULTI, EXEC and DISCARD
  24. */
  25. public function testThrowsExceptionOnUnsupportedMultiExecInProfile()
  26. {
  27. $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
  28. $client = new Client($connection, array('profile' => '1.2'));
  29. $tx = new MultiExec($client);
  30. }
  31. /**
  32. * @group disconnected
  33. * @expectedException Predis\NotSupportedException
  34. * @expectedExceptionMessage The current profile does not support WATCH and UNWATCH
  35. */
  36. public function testThrowsExceptionOnUnsupportedWatchUnwatchInProfile()
  37. {
  38. $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
  39. $client = new Client($connection, array('profile' => '2.0'));
  40. $tx = new MultiExec($client, array('options' => 'cas'));
  41. $tx->watch('foo');
  42. }
  43. /**
  44. * @group disconnected
  45. */
  46. public function testExecutionWithFluentInterface()
  47. {
  48. $commands = array();
  49. $expected = array('one', 'two', 'three');
  50. $callback = $this->getExecuteCallback($expected, $commands);
  51. $tx = $this->getMockedTransaction($callback);
  52. $this->assertSame($expected, $tx->echo('one')->echo('two')->echo('three')->execute());
  53. $this->assertSame(array('MULTI', 'ECHO', 'ECHO', 'ECHO', 'EXEC'), self::commandsToIDs($commands));
  54. }
  55. /**
  56. * @group disconnected
  57. */
  58. public function testExecutionWithCallable()
  59. {
  60. $commands = array();
  61. $expected = array('one', 'two', 'three');
  62. $callback = $this->getExecuteCallback($expected, $commands);
  63. $tx = $this->getMockedTransaction($callback);
  64. $replies = $tx->execute(function ($tx) {
  65. $tx->echo('one');
  66. $tx->echo('two');
  67. $tx->echo('three');
  68. });
  69. $this->assertSame($expected, $replies);
  70. $this->assertSame(array('MULTI', 'ECHO', 'ECHO', 'ECHO', 'EXEC'), self::commandsToIDs($commands));
  71. }
  72. /**
  73. * @group disconnected
  74. */
  75. public function testCannotMixExecutionWithFluentInterfaceAndCallable()
  76. {
  77. $commands = array();
  78. $callback = $this->getExecuteCallback(null, $commands);
  79. $tx = $this->getMockedTransaction($callback);
  80. $exception = null;
  81. try {
  82. $tx->echo('foo')->execute(function ($tx) {
  83. $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. } catch (\Exception $ex) {
  353. // NOOP
  354. }
  355. $this->assertNull($replies, $expected);
  356. $this->assertSame(array('MULTI', 'SET', 'GET', 'DISCARD'), self::commandsToIDs($commands));
  357. }
  358. /**
  359. * @group disconnected
  360. */
  361. public function testHandlesServerExceptionsInBlock()
  362. {
  363. $commands = array();
  364. $expected = array('foobar', true);
  365. $callback = $this->getExecuteCallback($expected, $commands);
  366. $tx = $this->getMockedTransaction($callback);
  367. $replies = null;
  368. try {
  369. $replies = $tx->execute(function ($tx) {
  370. $tx->set('foo', 'bar');
  371. $tx->echo('ERR Invalid operation');
  372. $tx->get('foo');
  373. });
  374. } catch (Response\ServerException $ex) {
  375. $tx->discard();
  376. }
  377. $this->assertNull($replies);
  378. $this->assertSame(array('MULTI', 'SET', 'ECHO', 'DISCARD'), self::commandsToIDs($commands));
  379. }
  380. // ******************************************************************** //
  381. // ---- INTEGRATION TESTS --------------------------------------------- //
  382. // ******************************************************************** //
  383. /**
  384. * @group connected
  385. */
  386. public function testIntegrationHandlesStandardExceptionsInBlock()
  387. {
  388. $client = $this->getClient();
  389. $exception = null;
  390. try {
  391. $client->transaction(function ($tx) {
  392. $tx->set('foo', 'bar');
  393. throw new \RuntimeException("TEST");
  394. });
  395. } catch (\Exception $ex) {
  396. $exception = $ex;
  397. }
  398. $this->assertInstanceOf('RuntimeException', $exception);
  399. $this->assertFalse($client->exists('foo'));
  400. }
  401. /**
  402. * @group connected
  403. */
  404. public function testIntegrationThrowsExceptionOnRedisErrorInBlock()
  405. {
  406. $client = $this->getClient();
  407. $exception = null;
  408. $value = (string) rand();
  409. try {
  410. $client->transaction(function ($tx) use ($value) {
  411. $tx->set('foo', 'bar');
  412. $tx->lpush('foo', 'bar');
  413. $tx->set('foo', $value);
  414. });
  415. } catch (Response\ServerException $ex) {
  416. $exception = $ex;
  417. }
  418. $this->assertInstanceOf('Predis\Response\ErrorInterface', $exception);
  419. $this->assertSame($value, $client->get('foo'));
  420. }
  421. /**
  422. * @group connected
  423. */
  424. public function testIntegrationReturnsErrorObjectOnRedisErrorInBlock()
  425. {
  426. $client = $this->getClient(array(), array('exceptions' => false));
  427. $replies = $client->transaction(function ($tx) {
  428. $tx->set('foo', 'bar');
  429. $tx->lpush('foo', 'bar');
  430. $tx->echo('foobar');
  431. });
  432. $this->assertTrue($replies[0]);
  433. $this->assertInstanceOf('Predis\Response\ErrorInterface', $replies[1]);
  434. $this->assertSame('foobar', $replies[2]);
  435. }
  436. /**
  437. * @group connected
  438. */
  439. public function testIntegrationSendMultiOnCommandsAfterDiscard()
  440. {
  441. $client = $this->getClient();
  442. $replies = $client->transaction(function ($tx) {
  443. $tx->set('foo', 'bar');
  444. $tx->discard();
  445. $tx->set('hoge', 'piyo');
  446. });
  447. $this->assertSame(1, count($replies));
  448. $this->assertFalse($client->exists('foo'));
  449. $this->assertTrue($client->exists('hoge'));
  450. }
  451. /**
  452. * @group connected
  453. */
  454. public function testIntegrationWritesOnWatchedKeysAbortTransaction()
  455. {
  456. $exception = null;
  457. $client1 = $this->getClient();
  458. $client2 = $this->getClient();
  459. try {
  460. $client1->transaction(array('watch' => 'sentinel'), function ($tx) use ($client2) {
  461. $tx->set('sentinel', 'client1');
  462. $tx->get('sentinel');
  463. $client2->set('sentinel', 'client2');
  464. });
  465. } catch (AbortedMultiExecException $ex) {
  466. $exception = $ex;
  467. }
  468. $this->assertInstanceOf('Predis\Transaction\AbortedMultiExecException', $exception);
  469. $this->assertSame('client2', $client1->get('sentinel'));
  470. }
  471. /**
  472. * @group connected
  473. */
  474. public function testIntegrationCheckAndSetWithDiscardAndRetry()
  475. {
  476. $client = $this->getClient();
  477. $client->set('foo', 'bar');
  478. $options = array('watch' => 'foo', 'cas' => true);
  479. $replies = $client->transaction($options, function ($tx) {
  480. $tx->watch('foobar');
  481. $foo = $tx->get('foo');
  482. $tx->multi();
  483. $tx->set('foobar', $foo);
  484. $tx->discard();
  485. $tx->mget('foo', 'foobar');
  486. });
  487. $this->assertInternalType('array', $replies);
  488. $this->assertSame(array(array('bar', null)), $replies);
  489. $hijack = true;
  490. $client2 = $this->getClient();
  491. $client->set('foo', 'bar');
  492. $options = array('watch' => 'foo', 'cas' => true, 'retry' => 1);
  493. $replies = $client->transaction($options, function ($tx) use ($client2, &$hijack) {
  494. $foo = $tx->get('foo');
  495. $tx->multi();
  496. $tx->set('foobar', $foo);
  497. $tx->discard();
  498. if ($hijack) {
  499. $hijack = false;
  500. $client2->set('foo', 'hijacked!');
  501. }
  502. $tx->mget('foo', 'foobar');
  503. });
  504. $this->assertInternalType('array', $replies);
  505. $this->assertSame(array(array('hijacked!', null)), $replies);
  506. }
  507. // ******************************************************************** //
  508. // ---- HELPER METHODS ------------------------------------------------ //
  509. // ******************************************************************** //
  510. /**
  511. * Returns a mocked instance of Predis\Connection\SingleConnectionInterface
  512. * usingthe specified callback to return values from executeCommand().
  513. *
  514. * @param \Closure $executeCallback
  515. * @return \Predis\Connection\SingleConnectionInterface
  516. */
  517. protected function getMockedConnection($executeCallback)
  518. {
  519. $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
  520. $connection->expects($this->any())
  521. ->method('executeCommand')
  522. ->will($this->returnCallback($executeCallback));
  523. return $connection;
  524. }
  525. /**
  526. * Returns a mocked instance of Predis\Transaction\MultiExec using
  527. * the specified callback to return values from the executeCommand method
  528. * of the underlying connection.
  529. *
  530. * @param \Closure $executeCallback
  531. * @return MultiExec
  532. */
  533. protected function getMockedTransaction($executeCallback, $options = array())
  534. {
  535. $connection = $this->getMockedConnection($executeCallback);
  536. $client = new Client($connection);
  537. $transaction = new MultiExec($client, $options);
  538. return $transaction;
  539. }
  540. /**
  541. * Returns a callback that emulates a server-side MULTI/EXEC transaction context.
  542. *
  543. * @param array $expected Expected replies.
  544. * @param array $commands Reference to an array that stores the whole flow of commands.
  545. * @return \Closure
  546. */
  547. protected function getExecuteCallback($expected = array(), &$commands = array(), &$cas = array())
  548. {
  549. $multi = $watch = $abort = false;
  550. return function (CommandInterface $command) use (&$expected, &$commands, &$cas, &$multi, &$watch, &$abort) {
  551. $cmd = $command->getId();
  552. if ($multi || $cmd === 'MULTI') {
  553. $commands[] = $command;
  554. } else {
  555. $cas[] = $command;
  556. }
  557. switch ($cmd) {
  558. case 'WATCH':
  559. if ($multi) {
  560. throw new Response\ServerException("ERR $cmd inside MULTI is not allowed");
  561. }
  562. return $watch = true;
  563. case 'MULTI':
  564. if ($multi) {
  565. throw new Response\ServerException("ERR MULTI calls can not be nested");
  566. }
  567. return $multi = true;
  568. case 'EXEC':
  569. if (!$multi) {
  570. throw new Response\ServerException("ERR $cmd without MULTI");
  571. }
  572. $watch = $multi = false;
  573. if ($abort) {
  574. $commands = $cas = array();
  575. $abort = false;
  576. return null;
  577. }
  578. return $expected;
  579. case 'DISCARD':
  580. if (!$multi) {
  581. throw new Response\ServerException("ERR $cmd without MULTI");
  582. }
  583. $watch = $multi = false;
  584. return true;
  585. case 'ECHO':
  586. @list($trigger) = $command->getArguments();
  587. if (strpos($trigger, 'ERR ') === 0) {
  588. throw new Response\ServerException($trigger);
  589. }
  590. if ($trigger === '!!ABORT!!' && $multi) {
  591. $abort = true;
  592. }
  593. return new Response\StatusQueued();
  594. case 'UNWATCH':
  595. $watch = false;
  596. default:
  597. return $multi ? new Response\StatusQueued() : 'DUMMY_REPLY';
  598. }
  599. };
  600. }
  601. /**
  602. * Converts an array of instances of Predis\Command\CommandInterface and
  603. * returns an array containing their IDs.
  604. *
  605. * @param array $commands List of commands instances.
  606. * @return array
  607. */
  608. protected static function commandsToIDs($commands) {
  609. return array_map(function($cmd) { return $cmd->getId(); }, $commands);
  610. }
  611. /**
  612. * Returns a client instance connected to the specified Redis
  613. * server instance to perform integration tests.
  614. *
  615. * @param array Additional connection parameters.
  616. * @param array Additional client options.
  617. * @return Client client instance.
  618. */
  619. protected function getClient(array $parameters = array(), array $options = array())
  620. {
  621. $parameters = array_merge(array(
  622. 'scheme' => 'tcp',
  623. 'host' => REDIS_SERVER_HOST,
  624. 'port' => REDIS_SERVER_PORT,
  625. 'database' => REDIS_SERVER_DBNUM,
  626. ), $parameters);
  627. $options = array_merge(array(
  628. 'profile' => REDIS_SERVER_VERSION
  629. ), $options);
  630. $client = new Client($parameters, $options);
  631. $client->connect();
  632. $client->flushdb();
  633. return $client;
  634. }
  635. }