diff --git a/lib/Predis/Transaction/MultiExec.php b/lib/Predis/Transaction/MultiExec.php index 8bf7757b..504152f9 100644 --- a/lib/Predis/Transaction/MultiExec.php +++ b/lib/Predis/Transaction/MultiExec.php @@ -30,13 +30,6 @@ use Predis\Protocol\ProtocolException; */ class MultiExec implements BasicClientInterface, ExecutableContextInterface { - const STATE_RESET = 0; // 0b00000 - const STATE_INITIALIZED = 1; // 0b00001 - const STATE_INSIDEBLOCK = 2; // 0b00010 - const STATE_DISCARDED = 4; // 0b00100 - const STATE_CAS = 8; // 0b01000 - const STATE_WATCH = 16; // 0b10000 - private $state; private $canWatch; @@ -51,62 +44,14 @@ class MultiExec implements BasicClientInterface, ExecutableContextInterface public function __construct(ClientInterface $client, array $options = null) { $this->checkCapabilities($client); + $this->options = $options ?: array(); $this->client = $client; + $this->state = new MultiExecState(); + $this->reset(); } - /** - * Sets the internal state flags. - * - * @param int $flags Set of flags - */ - protected function setState($flags) - { - $this->state = $flags; - } - - /** - * Gets the internal state flags. - * - * @return int - */ - protected function getState() - { - return $this->state; - } - - /** - * Sets one or more flags. - * - * @param int $flags Set of flags - */ - protected function flagState($flags) - { - $this->state |= $flags; - } - - /** - * Resets one or more flags. - * - * @param int $flags Set of flags - */ - protected function unflagState($flags) - { - $this->state &= ~$flags; - } - - /** - * Checks is a flag is set. - * - * @param int $flags Flag - * @return Boolean - */ - protected function checkState($flags) - { - return ($this->state & $flags) === $flags; - } - /** * Checks if the passed client instance satisfies the required conditions * needed to initialize the transaction object. @@ -147,7 +92,7 @@ class MultiExec implements BasicClientInterface, ExecutableContextInterface */ protected function reset() { - $this->setState(self::STATE_RESET); + $this->state->reset(); $this->commands = new SplQueue(); } @@ -156,32 +101,32 @@ class MultiExec implements BasicClientInterface, ExecutableContextInterface */ protected function initialize() { - if ($this->checkState(self::STATE_INITIALIZED)) { + if ($this->state->isInitialized()) { return; } $options = $this->options; if (isset($options['cas']) && $options['cas']) { - $this->flagState(self::STATE_CAS); + $this->state->flag(MultiExecState::CAS); } if (isset($options['watch'])) { $this->watch($options['watch']); } - $cas = $this->checkState(self::STATE_CAS); - $discarded = $this->checkState(self::STATE_DISCARDED); + $cas = $this->state->isCAS(); + $discarded = $this->state->isDiscarded(); if (!$cas || ($cas && $discarded)) { $this->client->multi(); if ($discarded) { - $this->unflagState(self::STATE_CAS); + $this->state->unflag(MultiExecState::CAS); } } - $this->unflagState(self::STATE_DISCARDED); - $this->flagState(self::STATE_INITIALIZED); + $this->state->unflag(MultiExecState::DISCARDED); + $this->state->flag(MultiExecState::INITIALIZED); } /** @@ -210,7 +155,7 @@ class MultiExec implements BasicClientInterface, ExecutableContextInterface $this->initialize(); $response = $this->client->executeCommand($command); - if ($this->checkState(self::STATE_CAS)) { + if ($this->state->isCAS()) { return $response; } @@ -233,12 +178,12 @@ class MultiExec implements BasicClientInterface, ExecutableContextInterface { $this->isWatchSupported(); - if ($this->checkState(self::STATE_INITIALIZED) && !$this->checkState(self::STATE_CAS)) { + if ($this->state->isWatchAllowed()) { throw new ClientException('WATCH after MULTI is not allowed'); } $reply = $this->client->watch($keys); - $this->flagState(self::STATE_WATCH); + $this->state->flag(MultiExecState::WATCH); return $reply; } @@ -250,8 +195,8 @@ class MultiExec implements BasicClientInterface, ExecutableContextInterface */ public function multi() { - if ($this->checkState(self::STATE_INITIALIZED | self::STATE_CAS)) { - $this->unflagState(self::STATE_CAS); + if ($this->state->check(MultiExecState::INITIALIZED | MultiExecState::CAS)) { + $this->state->unflag(MultiExecState::CAS); $this->client->multi(); } else { $this->initialize(); @@ -268,7 +213,7 @@ class MultiExec implements BasicClientInterface, ExecutableContextInterface public function unwatch() { $this->isWatchSupported(); - $this->unflagState(self::STATE_WATCH); + $this->state->unflag(MultiExecState::WATCH); $this->__call('unwatch', array()); return $this; @@ -282,11 +227,11 @@ class MultiExec implements BasicClientInterface, ExecutableContextInterface */ public function discard() { - if ($this->checkState(self::STATE_INITIALIZED)) { - $command = $this->checkState(self::STATE_CAS) ? 'unwatch' : 'discard'; + if ($this->state->isInitialized()) { + $command = $this->state->isCAS() ? 'unwatch' : 'discard'; $this->client->$command(); $this->reset(); - $this->flagState(self::STATE_DISCARDED); + $this->state->flag(MultiExecState::DISCARDED); } return $this; @@ -309,7 +254,7 @@ class MultiExec implements BasicClientInterface, ExecutableContextInterface */ private function checkBeforeExecution($callable) { - if ($this->checkState(self::STATE_INSIDEBLOCK)) { + if ($this->state->isExecuting()) { throw new ClientException("Cannot invoke 'execute' or 'exec' inside an active client transaction block"); } @@ -350,7 +295,7 @@ class MultiExec implements BasicClientInterface, ExecutableContextInterface } if ($this->commands->isEmpty()) { - if ($this->checkState(self::STATE_WATCH)) { + if ($this->state->isWatching()) { $this->discard(); } @@ -409,7 +354,7 @@ class MultiExec implements BasicClientInterface, ExecutableContextInterface protected function executeTransactionBlock($callable) { $blockException = null; - $this->flagState(self::STATE_INSIDEBLOCK); + $this->state->flag(MultiExecState::INSIDEBLOCK); try { call_user_func($callable, $this); @@ -422,7 +367,7 @@ class MultiExec implements BasicClientInterface, ExecutableContextInterface $this->discard(); } - $this->unflagState(self::STATE_INSIDEBLOCK); + $this->state->unflag(MultiExecState::INSIDEBLOCK); if ($blockException !== null) { throw $blockException; diff --git a/lib/Predis/Transaction/MultiExecState.php b/lib/Predis/Transaction/MultiExecState.php new file mode 100644 index 00000000..0ac4b5b8 --- /dev/null +++ b/lib/Predis/Transaction/MultiExecState.php @@ -0,0 +1,165 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Transaction; + +/** + * Utility class used to track the state of a MULTI / EXEC transaction. + * + * @author Daniele Alessandri + */ +class MultiExecState +{ + const INITIALIZED = 1; // 0b00001 + const INSIDEBLOCK = 2; // 0b00010 + const DISCARDED = 4; // 0b00100 + const CAS = 8; // 0b01000 + const WATCH = 16; // 0b10000 + + private $flags; + + /** + * + */ + public function __construct() + { + $this->flags = 0; + } + + /** + * Sets the internal state flags. + * + * @param int $flags Set of flags + */ + public function set($flags) + { + $this->flags = $flags; + } + + /** + * Gets the internal state flags. + * + * @return int + */ + public function get() + { + return $this->flags; + } + + /** + * Sets one or more flags. + * + * @param int $flags Set of flags + */ + public function flag($flags) + { + $this->flags |= $flags; + } + + /** + * Resets one or more flags. + * + * @param int $flags Set of flags + */ + public function unflag($flags) + { + $this->flags &= ~$flags; + } + + /** + * Returns if the specified flag or set of flags is set. + * + * @param int $flags Flag + * @return bool + */ + public function check($flags) + { + return ($this->flags & $flags) === $flags; + } + + /** + * Resets the state of a transaction. + */ + public function reset() + { + $this->flags = 0; + } + + /** + * Returns the state of the RESET flag. + * + * @return bool + */ + public function isReset() + { + return $this->flags === 0; + } + + /** + * Returns the state of the INITIALIZED flag. + * + * @return bool + */ + public function isInitialized() + { + return $this->check(self::INITIALIZED); + } + + /** + * Returns the state of the INSIDEBLOCK flag. + * + * @return bool + */ + public function isExecuting() + { + return $this->check(self::INSIDEBLOCK); + } + + /** + * Returns the state of the CAS flag. + * + * @return bool + */ + public function isCAS() + { + return $this->check(self::CAS); + } + + /** + * Returns if WATCH is allowed in the current state. + * + * @return bool + */ + public function isWatchAllowed() + { + return $this->check(self::INITIALIZED) && !$this->check(self::CAS); + } + + /** + * Returns the state of the WATCH flag. + * + * @return bool + */ + public function isWatching() + { + return $this->check(self::WATCH); + } + + /** + * Returns the state of the DISCARDED flag. + * + * @return bool + */ + public function isDiscarded() + { + return $this->check(self::DISCARDED); + } +} diff --git a/tests/Predis/Transaction/MultiExecStateTest.php b/tests/Predis/Transaction/MultiExecStateTest.php new file mode 100644 index 00000000..2607a064 --- /dev/null +++ b/tests/Predis/Transaction/MultiExecStateTest.php @@ -0,0 +1,202 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Transaction; + +use PHPUnit_Framework_TestCase as StandardTestCase; + +/** + * @group realm-transaction + */ +class MultiExecStateTest extends StandardTestCase +{ + /** + * @group disconnected + */ + public function testFlagsValues() + { + $this->assertSame(1, MultiExecState::INITIALIZED); + $this->assertSame(2, MultiExecState::INSIDEBLOCK); + $this->assertSame(4, MultiExecState::DISCARDED); + $this->assertSame(8, MultiExecState::CAS); + $this->assertSame(16, MultiExecState::WATCH); + } + + /** + * @group disconnected + */ + public function testStateConstructorStartsWithResetState() + { + $state = new MultiExecState(); + + $this->assertSame(0, $state->get()); + $this->assertTrue($state->isReset()); + } + + /** + * @group disconnected + */ + public function testCanCheckOneOrMoreStateFlags() + { + $flags = MultiExecState::INITIALIZED | MultiExecState::CAS; + $state = new MultiExecState(); + $state->set($flags); + + $this->assertSame($flags, $state->get()); + + $this->assertFalse($state->check(MultiExecState::INSIDEBLOCK)); + $this->assertTrue($state->check(MultiExecState::INITIALIZED)); + $this->assertTrue($state->check(MultiExecState::CAS)); + + $this->assertTrue($state->check($flags)); + $this->assertFalse($state->check($flags | MultiExecState::INSIDEBLOCK)); + } + + /** + * @group disconnected + */ + public function testSettingAndGettingWholeFlags() + { + $flags = MultiExecState::INITIALIZED | MultiExecState::CAS; + $state = new MultiExecState(); + $state->set($flags); + + $this->assertFalse($state->check(MultiExecState::INSIDEBLOCK)); + $this->assertTrue($state->check(MultiExecState::INITIALIZED)); + $this->assertTrue($state->check(MultiExecState::CAS)); + $this->assertSame($flags, $state->get()); + } + + /** + * @group disconnected + */ + public function testCanFlagSingleStates() + { + $flags = MultiExecState::INITIALIZED | MultiExecState::CAS; + $state = new MultiExecState(); + + $state->flag(MultiExecState::INITIALIZED); + $this->assertTrue($state->check(MultiExecState::INITIALIZED)); + $this->assertFalse($state->check(MultiExecState::CAS)); + + $state->flag(MultiExecState::CAS); + $this->assertTrue($state->check(MultiExecState::INITIALIZED)); + $this->assertTrue($state->check(MultiExecState::CAS)); + + $this->assertSame($flags, $state->get()); + } + + /** + * @group disconnected + */ + public function testCanUnflagSingleStates() + { + $state = new MultiExecState(); + $state->set(MultiExecState::INITIALIZED | MultiExecState::CAS); + + $this->assertTrue($state->check(MultiExecState::INITIALIZED)); + $this->assertTrue($state->check(MultiExecState::CAS)); + + $state->unflag(MultiExecState::CAS); + $this->assertTrue($state->check(MultiExecState::INITIALIZED)); + $this->assertFalse($state->check(MultiExecState::CAS)); + + $state->unflag(MultiExecState::INITIALIZED); + $this->assertFalse($state->check(MultiExecState::INITIALIZED)); + $this->assertFalse($state->check(MultiExecState::CAS)); + + $this->assertTrue($state->isReset()); + } + + /** + * @group disconnected + */ + public function testIsInitializedMethod() + { + $state = new MultiExecState(); + + $this->assertFalse($state->isInitialized()); + + $state->set(MultiExecState::INITIALIZED); + $this->assertTrue($state->isInitialized()); + } + + /** + * @group disconnected + */ + public function testIsExecuting() + { + $state = new MultiExecState(); + + $this->assertFalse($state->isExecuting()); + + $state->set(MultiExecState::INSIDEBLOCK); + $this->assertTrue($state->isExecuting()); + } + + /** + * @group disconnected + */ + public function testIsCAS() + { + $state = new MultiExecState(); + + $this->assertFalse($state->isCAS()); + + $state->set(MultiExecState::CAS); + $this->assertTrue($state->isCAS()); + } + + /** + * @group disconnected + */ + public function testIsWatchAllowed() + { + $state = new MultiExecState(); + + $this->assertFalse($state->isWatchAllowed()); + + $state->flag(MultiExecState::INITIALIZED); + $this->assertTrue($state->isWatchAllowed()); + + $state->flag(MultiExecState::CAS); + $this->assertFalse($state->isWatchAllowed()); + + $state->unflag(MultiExecState::CAS); + $this->assertTrue($state->isWatchAllowed()); + } + + /** + * @group disconnected + */ + public function testIsWatching() + { + $state = new MultiExecState(); + + $this->assertFalse($state->isWatching()); + + $state->set(MultiExecState::WATCH); + $this->assertTrue($state->isWatching()); + } + + /** + * @group disconnected + */ + public function testIsDiscarded() + { + $state = new MultiExecState(); + + $this->assertFalse($state->isDiscarded()); + + $state->set(MultiExecState::DISCARDED); + $this->assertTrue($state->isDiscarded()); + } +}