Browse Source

Extract the tracking of transaction states to a separate class.

The Predis\Transaction\MultiExecState class is actually quite dummy as
it does not work as a finite-state machine, but is used to track state
using flags. It basically replicates the same behaviour of when it was
part of the Predis\Transaction\MultiExec code and it will do for now.
Daniele Alessandri 11 years ago
parent
commit
84e0f5af7a

+ 24 - 79
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,60 +44,12 @@ class MultiExec implements BasicClientInterface, ExecutableContextInterface
     public function __construct(ClientInterface $client, array $options = null)
     {
         $this->checkCapabilities($client);
+
         $this->options = $options ?: array();
         $this->client = $client;
-        $this->reset();
-    }
+        $this->state = new MultiExecState();
 
-    /**
-     * 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;
+        $this->reset();
     }
 
     /**
@@ -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;

+ 165 - 0
lib/Predis/Transaction/MultiExecState.php

@@ -0,0 +1,165 @@
+<?php
+
+/*
+ * This file is part of the Predis package.
+ *
+ * (c) Daniele Alessandri <suppakilla@gmail.com>
+ *
+ * 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 <suppakilla@gmail.com>
+ */
+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);
+    }
+}

+ 202 - 0
tests/Predis/Transaction/MultiExecStateTest.php

@@ -0,0 +1,202 @@
+<?php
+
+/*
+ * This file is part of the Predis package.
+ *
+ * (c) Daniele Alessandri <suppakilla@gmail.com>
+ *
+ * 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());
+    }
+}