Просмотр исходного кода

Merge pull request #13 from alcaeus/write-concern-read-preference-helpers

Add helpers for readPreference and writeConcern
Andreas 10 лет назад
Родитель
Сommit
9578a49648

+ 5 - 69
lib/Alcaeus/MongoDbAdapter/AbstractCursor.php

@@ -14,15 +14,18 @@
  */
 
 namespace Alcaeus\MongoDbAdapter;
+
+use Alcaeus\MongoDbAdapter\Helper\ReadPreference;
 use MongoDB\Collection;
 use MongoDB\Driver\Cursor;
-use MongoDB\Driver\ReadPreference;
 
 /**
  * @internal
  */
 abstract class AbstractCursor
 {
+    use ReadPreference;
+
     /**
      * @var int
      */
@@ -67,11 +70,6 @@ abstract class AbstractCursor
     ];
 
     /**
-     * @var array
-     */
-    protected $readPreference = [];
-
-    /**
      * @return Cursor
      */
     abstract protected function ensureCursor();
@@ -188,16 +186,6 @@ abstract class AbstractCursor
     }
 
     /**
-     * Get the read preference for this query
-     * @link http://www.php.net/manual/en/mongocursor.getreadpreference.php
-     * @return array
-     */
-    public function getReadPreference()
-    {
-        return $this->readPreference;
-    }
-
-    /**
      * @return array
      */
     public function info()
@@ -213,27 +201,7 @@ abstract class AbstractCursor
      */
     public function setReadPreference($readPreference, $tags = null)
     {
-        $availableReadPreferences = [
-            \MongoClient::RP_PRIMARY,
-            \MongoClient::RP_PRIMARY_PREFERRED,
-            \MongoClient::RP_SECONDARY,
-            \MongoClient::RP_SECONDARY_PREFERRED,
-            \MongoClient::RP_NEAREST
-        ];
-        if (! in_array($readPreference, $availableReadPreferences)) {
-            trigger_error("The value '$readPreference' is not valid as read preference type", E_WARNING);
-            return $this;
-        }
-
-        if ($readPreference == \MongoClient::RP_PRIMARY && count($tags)) {
-            trigger_error("You can't use read preference tags with a read preference of PRIMARY", E_WARNING);
-            return $this;
-        }
-
-        $this->readPreference = [
-            'type' => $readPreference,
-            'tagsets' => $tags
-        ];
+        $this->setReadPreferenceFromParameters($readPreference, $tags);
 
         return $this;
     }
@@ -278,38 +246,6 @@ abstract class AbstractCursor
     }
 
     /**
-     * @return ReadPreference|null
-     */
-    protected function convertReadPreference()
-    {
-        $type = array_key_exists('type', $this->readPreference) ? $this->readPreference['type'] : null;
-        if ($type === null) {
-            return null;
-        }
-
-        switch ($type) {
-            case \MongoClient::RP_PRIMARY_PREFERRED:
-                $mode = ReadPreference::RP_PRIMARY_PREFERRED;
-                break;
-            case \MongoClient::RP_SECONDARY:
-                $mode = ReadPreference::RP_SECONDARY;
-                break;
-            case \MongoClient::RP_SECONDARY_PREFERRED:
-                $mode = ReadPreference::RP_SECONDARY_PREFERRED;
-                break;
-            case \MongoClient::RP_NEAREST:
-                $mode = ReadPreference::RP_NEAREST;
-                break;
-            default:
-                $mode = ReadPreference::RP_PRIMARY;
-        }
-
-        $tagSets = array_key_exists('tagsets', $this->readPreference) ? $this->readPreference['tagsets'] : [];
-
-        return new ReadPreference($mode, $tagSets);
-    }
-
-    /**
      * @return \IteratorIterator
      */
     protected function ensureIterator()

+ 126 - 0
lib/Alcaeus/MongoDbAdapter/Helper/ReadPreference.php

@@ -0,0 +1,126 @@
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Alcaeus\MongoDbAdapter\Helper;
+
+/**
+ * @internal
+ */
+trait ReadPreference
+{
+    /**
+     * @var \MongoDB\Driver\ReadPreference
+     */
+    protected $readPreference;
+
+    /**
+     * @param string $readPreference
+     * @param array $tags
+     * @return bool
+     */
+    abstract public function setReadPreference($readPreference, $tags = null);
+
+    /**
+     * @return array
+     */
+    public function getReadPreference()
+    {
+        if ($this->readPreference === null) {
+            $this->readPreference = new \MongoDB\Driver\ReadPreference(\MongoDB\Driver\ReadPreference::RP_PRIMARY);
+        }
+
+        $mode = $this->readPreference->getMode();
+
+        switch ($mode) {
+            case \MongoDB\Driver\ReadPreference::RP_PRIMARY_PREFERRED:
+                $type = \MongoClient::RP_PRIMARY_PREFERRED;
+                break;
+            case \MongoDB\Driver\ReadPreference::RP_SECONDARY:
+                $type = \MongoClient::RP_SECONDARY;
+                break;
+            case \MongoDB\Driver\ReadPreference::RP_SECONDARY_PREFERRED:
+                $type = \MongoClient::RP_SECONDARY_PREFERRED;
+                break;
+            case \MongoDB\Driver\ReadPreference::RP_NEAREST:
+                $type = \MongoClient::RP_NEAREST;
+                break;
+            default:
+                $type = \MongoClient::RP_PRIMARY;
+        }
+
+        $readPreference = ['type' => $type];
+        if ($this->readPreference->getTagSets() !== null && $this->readPreference->getTagSets() !== []) {
+            $readPreference['tagsets'] = $this->readPreference->getTagSets();
+        }
+
+        return $readPreference;
+    }
+
+    /**
+     * @param string $readPreference
+     * @param array $tags
+     * @return bool
+     */
+    protected function setReadPreferenceFromParameters($readPreference, $tags = null)
+    {
+        // @internal Passing an array for $readPreference is necessary to avoid conversion voodoo
+        // It should not be used externally!
+        if (is_array($readPreference)) {
+            return $this->setReadPreferenceFromArray($readPreference);
+        }
+
+        switch ($readPreference) {
+            case \MongoClient::RP_PRIMARY:
+                $mode = \MongoDB\Driver\ReadPreference::RP_PRIMARY;
+                break;
+            case \MongoClient::RP_PRIMARY_PREFERRED:
+                $mode = \MongoDB\Driver\ReadPreference::RP_PRIMARY_PREFERRED;
+                break;
+            case \MongoClient::RP_SECONDARY:
+                $mode = \MongoDB\Driver\ReadPreference::RP_SECONDARY;
+                break;
+            case \MongoClient::RP_SECONDARY_PREFERRED:
+                $mode = \MongoDB\Driver\ReadPreference::RP_SECONDARY_PREFERRED;
+                break;
+            case \MongoClient::RP_NEAREST:
+                $mode = \MongoDB\Driver\ReadPreference::RP_NEAREST;
+                break;
+            default:
+                trigger_error("The value '$readPreference' is not valid as read preference type", E_WARNING);
+                return false;
+        }
+
+        if ($readPreference == \MongoClient::RP_PRIMARY && count($tags)) {
+            trigger_error("You can't use read preference tags with a read preference of PRIMARY", E_WARNING);
+            return false;
+        }
+
+        $this->readPreference = new \MongoDB\Driver\ReadPreference($mode, $tags);
+
+        return true;
+    }
+
+    /**
+     * @param array $readPreferenceArray
+     * @return bool
+     */
+    protected function setReadPreferenceFromArray($readPreferenceArray)
+    {
+        $readPreference = $readPreferenceArray['type'];
+        $tags = isset($readPreferenceArray['tagsets']) ? $readPreferenceArray['tagsets'] : [];
+
+        return $this->setReadPreferenceFromParameters($readPreference, $tags);
+    }
+}

+ 80 - 0
lib/Alcaeus/MongoDbAdapter/Helper/WriteConcern.php

@@ -0,0 +1,80 @@
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Alcaeus\MongoDbAdapter\Helper;
+
+/**
+ * @internal
+ */
+trait WriteConcern
+{
+    /**
+     * @var \MongoDB\Driver\WriteConcern
+     */
+    protected $writeConcern;
+
+    /**
+     * @param $wstring
+     * @param int $wtimeout
+     * @return bool
+     */
+    abstract public function setWriteConcern($wstring, $wtimeout = 0);
+
+    /**
+     * @return array
+     */
+    public function getWriteConcern()
+    {
+        if ($this->writeConcern === null) {
+            $this->writeConcern = new \MongoDB\Driver\WriteConcern(1);
+        }
+
+        return [
+            'w' => $this->writeConcern->getW(),
+            'wtimeout' => $this->writeConcern->getWtimeout(),
+        ];
+
+    }
+
+    /**
+     * @param string|int $wstring
+     * @param int $wtimeout
+     * @return bool
+     */
+    protected function setWriteConcernFromParameters($wstring, $wtimeout = 0)
+    {
+        if (! is_string($wstring) && ! is_int($wstring)) {
+            trigger_error("w for WriteConcern must be a string or integer", E_WARNING);
+            return false;
+        }
+
+        // Ensure wtimeout is not < 0
+        $this->writeConcern = new \MongoDB\Driver\WriteConcern($wstring, max($wtimeout, 0));
+
+        return true;
+    }
+
+    /**
+     * @param array $writeConcernArray
+     * @return bool
+     */
+    protected function setWriteConcernFromArray($writeConcernArray)
+    {
+        $wstring = $writeConcernArray['w'];
+        $wtimeout = isset($writeConcernArray['wtimeout']) ? $writeConcernArray['wtimeout'] : 0;
+
+        return $this->setWriteConcernFromParameters($wstring, $wtimeout);
+    }
+}

+ 14 - 26
lib/Mongo/MongoClient.php

@@ -13,6 +13,7 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+use Alcaeus\MongoDbAdapter\Helper;
 use MongoDB\Client;
 
 /**
@@ -22,6 +23,9 @@ use MongoDB\Client;
  */
 class MongoClient
 {
+    use Helper\ReadPreference;
+    use Helper\WriteConcern;
+
     const VERSION = '1.6.12';
     const DEFAULT_HOST = "localhost" ;
     const DEFAULT_PORT = 27017 ;
@@ -172,26 +176,6 @@ class MongoClient
     }
 
     /**
-     * Get the read preference for this connection
-     *
-     * @return array
-     */
-    public function getReadPreference()
-    {
-        return [];
-    }
-
-    /**
-     * Get the write concern for this connection
-     *
-     * @return array Returns an array describing the write concern.
-     */
-    public function getWriteConcern()
-    {
-        return [];
-    }
-
-    /**
      * Kills a specific cursor on the server
      *
      * @link http://www.php.net/manual/en/mongoclient.killcursor.php
@@ -246,15 +230,19 @@ class MongoClient
     }
 
     /**
-     * Set read preference
-     *
-     * @param string $readPreference
-     * @param array $tags
-     * @return bool
+     * {@inheritdoc}
      */
     public function setReadPreference($readPreference, $tags = null)
     {
-        return false;
+        return $this->setReadPreferenceFromParameters($readPreference, $tags);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setWriteConcern($wstring, $wtimeout = 0)
+    {
+        return $this->setWriteConcernFromParameters($wstring, $wtimeout);
     }
 
     /**

+ 63 - 22
lib/Mongo/MongoCollection.php

@@ -13,6 +13,7 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+use Alcaeus\MongoDbAdapter\Helper;
 use Alcaeus\MongoDbAdapter\TypeConverter;
 
 /**
@@ -21,6 +22,9 @@ use Alcaeus\MongoDbAdapter\TypeConverter;
  */
 class MongoCollection
 {
+    use Helper\ReadPreference;
+    use Helper\WriteConcern;
+
     const ASCENDING = 1;
     const DESCENDING = -1;
 
@@ -40,16 +44,6 @@ class MongoCollection
     protected $collection;
 
     /**
-     * @var int<p>
-     */
-    public $w;
-
-    /**
-     * @var int <p>
-     */
-    public $wtimeout;
-
-    /**
      * Creates a new collection
      * @link http://www.php.net/manual/en/mongocollection.construct.php
      * @param MongoDB $db Parent database.
@@ -61,7 +55,11 @@ class MongoCollection
     {
         $this->db = $db;
         $this->name = $name;
-        $this->collection = $this->db->getDb()->selectCollection($name);
+
+        $this->setReadPreferenceFromArray($db->getReadPreference());
+        $this->setWriteConcernFromArray($db->getWriteConcern());
+
+        $this->createCollectionObject();
     }
 
     /**
@@ -93,10 +91,27 @@ class MongoCollection
      */
     public function __get($name)
     {
+        // Handle w and wtimeout properties that replicate data stored in $readPreference
+        if ($name === 'w' || $name === 'wtimeout') {
+            return $this->getWriteConcern()[$name];
+        }
+
         return $this->db->selectCollection($this->name . '.' . $name);
     }
 
     /**
+     * @param string $name
+     * @param mixed $value
+     */
+    public function __set($name, $value)
+    {
+        if ($name === 'w' || $name === 'wtimeout') {
+            $this->setWriteConcernFromArray([$name => $value] + $this->getWriteConcern());
+            $this->createCollectionObject();
+        }
+    }
+
+    /**
      * @link http://www.php.net/manual/en/mongocollection.aggregate.php
      * @param array $pipeline
      * @param array $op
@@ -154,7 +169,10 @@ class MongoCollection
 
         $command += $options;
 
-        return new MongoCommandCursor($this->db->getConnection(), (string) $this, $command);
+        $cursor = new MongoCommandCursor($this->db->getConnection(), (string)$this, $command);
+        $cursor->setReadPreference($this->getReadPreference());
+
+        return $cursor;
     }
 
     /**
@@ -187,22 +205,25 @@ class MongoCollection
     }
 
     /**
-     * @link http://www.php.net/manual/en/mongocollection.getreadpreference.php
-     * @return array
+     * {@inheritdoc}
      */
-    public function getReadPreference()
+    public function setReadPreference($readPreference, $tags = null)
     {
-        $this->notImplemented();
+        $result = $this->setReadPreferenceFromParameters($readPreference, $tags);
+        $this->createCollectionObject();
+
+        return $result;
     }
 
     /**
-     * @param string $read_preference
-     * @param array $tags
-     * @return bool
+     * {@inheritdoc}
      */
-    public function setReadPreference($read_preference, array $tags)
+    public function setWriteConcern($wstring, $wtimeout = 0)
     {
-        $this->notImplemented();
+        $result = $this->setWriteConcernFromParameters($wstring, $wtimeout);
+        $this->createCollectionObject();
+
+        return $result;
     }
 
     /**
@@ -351,7 +372,10 @@ class MongoCollection
      */
     public function find(array $query = array(), array $fields = array())
     {
-        return new MongoCursor($this->db->getConnection(), (string) $this, $query, $fields);
+        $cursor = new MongoCursor($this->db->getConnection(), (string)$this, $query, $fields);
+        $cursor->setReadPreference($this->getReadPreference());
+
+        return $cursor;
     }
 
     /**
@@ -512,5 +536,22 @@ class MongoCollection
     {
         throw new \Exception('Not implemented');
     }
+
+    /**
+     * @return \MongoDB\Collection
+     */
+    private function createCollectionObject()
+    {
+        $options = [
+            'readPreference' => $this->readPreference,
+            'writeConcern' => $this->writeConcern,
+        ];
+
+        if ($this->collection === null) {
+            $this->collection = $this->db->getDb()->selectCollection($this->name, $options);
+        } else {
+            $this->collection = $this->collection->withOptions($options);
+        }
+    }
 }
 

+ 5 - 13
lib/Mongo/MongoCursor.php

@@ -327,6 +327,11 @@ class MongoCursor extends AbstractCursor implements Iterator
     {
         $this->errorIfOpened();
         static::$slaveOkay = $okay;
+
+        $readPreferenceArray = $this->getReadPreference();
+        $readPreferenceArray['type'] = $okay ? \MongoClient::RP_SECONDARY_PREFERRED : \MongoClient::RP_PRIMARY;
+
+        $this->setReadPreferenceFromArray($readPreferenceArray);
     }
 
     /**
@@ -400,19 +405,6 @@ class MongoCursor extends AbstractCursor implements Iterator
     }
 
     /**
-     * {@inheritdoc}
-     */
-    protected function convertReadPreference()
-    {
-        $readPreference = parent::convertReadPreference();
-        if ($readPreference === null && static::$slaveOkay) {
-            $readPreference = new ReadPreference(ReadPreference::RP_SECONDARY_PREFERRED);
-        }
-
-        return $readPreference;
-    }
-
-    /**
      * @return Cursor
      */
     protected function ensureCursor()

+ 63 - 48
lib/Mongo/MongoDB.php

@@ -13,6 +13,7 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+use Alcaeus\MongoDbAdapter\Helper;
 use MongoDB\Model\CollectionInfo;
 
 /**
@@ -21,24 +22,27 @@ use MongoDB\Model\CollectionInfo;
  */
 class MongoDB
 {
+    use Helper\ReadPreference;
+    use Helper\WriteConcern;
+
     const PROFILING_OFF = 0;
     const PROFILING_SLOW = 1;
     const PROFILING_ON = 2;
 
     /**
-     * @var int
+     * @var MongoClient
      */
-    public $w = 1;
+    protected $connection;
 
     /**
-     * @var int
+     * @var \MongoDB\Database
      */
-    public $wtimeout = 10000;
+    protected $db;
 
     /**
-     * @var \MongoDB\Database
+     * @var string
      */
-    protected $db;
+    protected $name;
 
     /**
      * Creates a new database
@@ -50,11 +54,15 @@ class MongoDB
      * @throws Exception
      * @return MongoDB Returns the database.
      */
-    public function __construct($conn, $name)
+    public function __construct(MongoClient $conn, $name)
     {
         $this->connection = $conn;
         $this->name = $name;
-        $this->db = $this->connection->getClient()->selectDatabase($name);
+
+        $this->setReadPreferenceFromArray($conn->getReadPreference());
+        $this->setWriteConcernFromArray($conn->getWriteConcern());
+
+        $this->createDatabaseObject();
     }
 
     /**
@@ -77,18 +85,35 @@ class MongoDB
     }
 
     /**
-     * (PECL mongo &gt;= 1.0.2)<br/>
      * Gets a collection
+     *
      * @link http://www.php.net/manual/en/mongodb.get.php
      * @param string $name The name of the collection.
      * @return MongoCollection
      */
     public function __get($name)
     {
+        // Handle w and wtimeout properties that replicate data stored in $readPreference
+        if ($name === 'w' || $name === 'wtimeout') {
+            return $this->getWriteConcern()[$name];
+        }
+
         return $this->selectCollection($name);
     }
 
     /**
+     * @param string $name
+     * @param mixed $value
+     */
+    public function __set($name, $value)
+    {
+        if ($name === 'w' || $name === 'wtimeout') {
+            $this->setWriteConcernFromArray([$name => $value] + $this->getWriteConcern());
+            $this->createDatabaseObject();
+        }
+    }
+
+    /**
      * (PECL mongo &gt;= 1.3.0)<br/>
      * @link http://www.php.net/manual/en/mongodb.getcollectionnames.php
      * Get all collections from this database
@@ -335,19 +360,6 @@ class MongoDB
     }
 
     /**
-     * (PECL mongo &gt;= 1.5.0)<br/>
-     * Get the write concern for this database
-     * @link http://php.net/manual/en/mongodb.getwriteconcern.php
-     * @return array <p>This function returns an array describing the write concern.
-     * The array contains the values w for an integer acknowledgement level or string mode,
-     * and wtimeout denoting the maximum number of milliseconds to wait for the server to satisfy the write concern.</p>
-     */
-    public function getWriteConcern()
-    {
-        $this->notImplemented();
-    }
-
-    /**
      * (PECL mongo &gt;= 0.9.3)<br/>
      * Runs JavaScript code on the database server.
      * @link http://www.php.net/manual/en/mongodb.execute.php
@@ -381,6 +393,7 @@ class MongoDB
     {
         try {
             $cursor = new \MongoCommandCursor($this->connection, $this->name, $data);
+            $cursor->setReadPreference($this->getReadPreference());
 
             return iterator_to_array($cursor)[0];
         } catch (\MongoDB\Driver\Exception\RuntimeException $e) {
@@ -463,44 +476,46 @@ class MongoDB
     }
 
     /**
-     * (PECL mongo &gt;= 1.3.0)<br/>
-     * Get the read preference for this database
-     * @link http://www.php.net/manual/en/mongodb.getreadpreference.php
-     * @return array This function returns an array describing the read preference. The array contains the values type for the string read preference mode (corresponding to the MongoClient constants), and tagsets containing a list of all tag set criteria. If no tag sets were specified, tagsets will not be present in the array.
+     * {@inheritdoc}
      */
-    public function getReadPreference()
+    public function setReadPreference($readPreference, $tags = null)
     {
-        $this->notImplemented();
-    }
+        $result = $this->setReadPreferenceFromParameters($readPreference, $tags);
+        $this->createDatabaseObject();
 
-    /**
-     * (PECL mongo &gt;= 1.3.0)<br/>
-     * Set the read preference for this database
-     * @link http://www.php.net/manual/en/mongodb.setreadpreference.php
-     * @param string $read_preference <p>The read preference mode: <b>MongoClient::RP_PRIMARY</b>, <b>MongoClient::RP_PRIMARY_PREFERRED</b>, <b>MongoClient::RP_SECONDARY</b>, <b>MongoClient::RP_SECONDARY_PREFERRED</b>, or <b>MongoClient::RP_NEAREST</b>.</p>
-     * @param array $tags [optional] <p>An array of zero or more tag sets, where each tag set is itself an array of criteria used to match tags on replica set members.</p>
-     * @return boolean Returns <b>TRUE</b> on success, or <b>FALSE</b> otherwise.
-     */
-    public function setReadPreference($read_preference, array $tags)
-    {
-        $this->notImplemented();
+        return $result;
     }
 
     /**
-     * (PECL mongo &gt;= 1.5.0)<br/>
-     * @link http://php.net/manual/en/mongodb.setwriteconcern.php
-     * Set the write concern for this database
-     * @param mixed $w <p>The write concern. This may be an integer denoting the number of servers required to acknowledge the write, or a string mode (e.g. "majority").</p>
-     * @param int $wtimeout[optional] <p>The maximum number of milliseconds to wait for the server to satisfy the write concern.</p>
-     * @return boolean Returns <b>TRUE</b> on success, or <b>FALSE</b> otherwise.
+     * {@inheritdoc}
      */
-    public function setWriteConcern($w, $wtimeout)
+    public function setWriteConcern($wstring, $wtimeout = 0)
     {
-        $this->notImplemented();
+        $result = $this->setWriteConcernFromParameters($wstring, $wtimeout);
+        $this->createDatabaseObject();
+
+        return $result;
     }
 
     protected function notImplemented()
     {
         throw new \Exception('Not implemented');
     }
+
+    /**
+     * @return \MongoDB\Database
+     */
+    private function createDatabaseObject()
+    {
+        $options = [
+            'readPreference' => $this->readPreference,
+            'writeConcern' => $this->writeConcern,
+        ];
+
+        if ($this->db === null) {
+            $this->db = $this->connection->getClient()->selectDatabase($this->name, $options);
+        } else {
+            $this->db = $this->db->withOptions($options);
+        }
+    }
 }

+ 2 - 8
lib/Mongo/MongoId.php

@@ -23,8 +23,6 @@ class MongoId implements Serializable, TypeInterface
      */
     private $objectID;
 
-    private $attributes = [];
-
     /**
      * Creates a new id
      *
@@ -88,7 +86,7 @@ class MongoId implements Serializable, TypeInterface
             return (string) $this->objectID;
         }
 
-        return $this->attributes[$name];
+        return null;
     }
 
     /**
@@ -101,8 +99,6 @@ class MongoId implements Serializable, TypeInterface
             trigger_error("The '\$id' property is read-only", E_DEPRECATED);
             return;
         }
-
-        $this->attributes[$name] = $value;
     }
 
     /**
@@ -111,7 +107,7 @@ class MongoId implements Serializable, TypeInterface
      */
     public function __isset($name)
     {
-        return $name === 'id' || array_key_exists($name, $this->attributes);
+        return $name === 'id';
     }
 
     /**
@@ -123,8 +119,6 @@ class MongoId implements Serializable, TypeInterface
             trigger_error("The '\$id' property is read-only", E_DEPRECATED);
             return;
         }
-
-        unset($this->attributes[$name]);
     }
 
     /**

+ 18 - 0
tests/Alcaeus/MongoDbAdapter/MongoClientTest.php

@@ -46,6 +46,24 @@ class MongoClientTest extends TestCase
         $this->assertSame('mongo-php-adapter.test', (string) $collection);
     }
 
+    public function testReadPreference()
+    {
+        $client = $this->getClient();
+        $this->assertSame(['type' => \MongoClient::RP_PRIMARY], $client->getReadPreference());
+
+        $this->assertTrue($client->setReadPreference(\MongoClient::RP_SECONDARY, ['a' => 'b']));
+        $this->assertSame(['type' => \MongoClient::RP_SECONDARY, 'tagsets' => ['a' => 'b']], $client->getReadPreference());
+    }
+
+    public function testWriteConcern()
+    {
+        $client = $this->getClient();
+        $this->assertSame(['w' => 1, 'wtimeout' => 0], $client->getWriteConcern());
+
+        $this->assertTrue($client->setWriteConcern('majority', 100));
+        $this->assertSame(['w' => 'majority', 'wtimeout' => 100], $client->getWriteConcern());
+    }
+
     /**
      * @param array|null $options
      * @return \MongoClient

+ 66 - 1
tests/Alcaeus/MongoDbAdapter/MongoCollectionTest.php

@@ -1,6 +1,7 @@
 <?php
 
 namespace Alcaeus\MongoDbAdapter\Tests;
+use MongoDB\Driver\ReadPreference;
 
 /**
  * @author alcaeus <alcaeus@alcaeus.org>
@@ -136,14 +137,78 @@ class MongoCollectionTest extends TestCase
         ], iterator_to_array($cursor));
     }
 
+    public function testReadPreference()
+    {
+        $collection = $this->getCollection();
+        $this->assertSame(['type' => \MongoClient::RP_PRIMARY], $collection->getReadPreference());
+
+        $this->assertTrue($collection->setReadPreference(\MongoClient::RP_SECONDARY, ['a' => 'b']));
+        $this->assertSame(['type' => \MongoClient::RP_SECONDARY, 'tagsets' => ['a' => 'b']], $collection->getReadPreference());
+
+        // Only way to check whether options are passed down is through debugInfo
+        $writeConcern = $collection->getCollection()->__debugInfo()['readPreference'];
+
+        $this->assertSame(ReadPreference::RP_SECONDARY, $writeConcern->getMode());
+        $this->assertSame(['a' => 'b'], $writeConcern->getTagSets());
+    }
+
+    public function testReadPreferenceIsInherited()
+    {
+        $database = $this->getDatabase();
+        $database->setReadPreference(\MongoClient::RP_SECONDARY, ['a' => 'b']);
+
+        $collection = $database->selectCollection('test');
+        $this->assertSame(['type' => \MongoClient::RP_SECONDARY, 'tagsets' => ['a' => 'b']], $collection->getReadPreference());
+    }
+
+    public function testWriteConcern()
+    {
+        $collection = $this->getCollection();
+        $this->assertSame(['w' => 1, 'wtimeout' => 0], $collection->getWriteConcern());
+        $this->assertSame(1, $collection->w);
+        $this->assertSame(0, $collection->wtimeout);
+
+        $this->assertTrue($collection->setWriteConcern('majority', 100));
+        $this->assertSame(['w' => 'majority', 'wtimeout' => 100], $collection->getWriteConcern());
+
+        $collection->w = 2;
+        $this->assertSame(['w' => 2, 'wtimeout' => 100], $collection->getWriteConcern());
+
+        $collection->wtimeout = -1;
+        $this->assertSame(['w' => 2, 'wtimeout' => 0], $collection->getWriteConcern());
+
+        // Only way to check whether options are passed down is through debugInfo
+        $writeConcern = $collection->getCollection()->__debugInfo()['writeConcern'];
+
+        $this->assertSame(2, $writeConcern->getW());
+        $this->assertSame(0, $writeConcern->getWtimeout());
+    }
+
+    public function testWriteConcernIsInherited()
+    {
+        $database = $this->getDatabase();
+        $database->setWriteConcern('majority', 100);
+
+        $collection = $database->selectCollection('test');
+        $this->assertSame(['w' => 'majority', 'wtimeout' => 100], $collection->getWriteConcern());
+    }
+
     /**
      * @return \MongoCollection
      */
     protected function getCollection($name = 'test')
     {
+        return $this->getDatabase()->selectCollection($name);
+    }
+
+    /**
+     * @return \MongoDB
+     */
+    protected function getDatabase()
+    {
         $client = new \MongoClient();
 
-        return $client->selectCollection('mongo-php-adapter', $name);
+        return $client->selectDB('mongo-php-adapter');
     }
 
     /**

+ 9 - 0
tests/Alcaeus/MongoDbAdapter/MongoCursorTest.php

@@ -238,6 +238,15 @@ class MongoCursorTest extends TestCase
         $this->assertSame($expected, $cursor->info());
     }
 
+    public function testReadPreferenceIsInherited()
+    {
+        $collection = $this->getCollection();
+        $collection->setReadPreference(\MongoClient::RP_SECONDARY, ['a' => 'b']);
+
+        $cursor = $collection->find(['foo' => 'bar']);
+        $this->assertSame(['type' => \MongoClient::RP_SECONDARY, 'tagsets' => ['a' => 'b']], $cursor->getReadPreference());
+    }
+
     /**
      * @param string $name
      * @return \MongoCollection

+ 66 - 1
tests/Alcaeus/MongoDbAdapter/MongoDBTest.php

@@ -1,6 +1,7 @@
 <?php
 
 namespace Alcaeus\MongoDbAdapter\Tests;
+use MongoDB\Driver\ReadPreference;
 
 /**
  * @author alcaeus <alcaeus@alcaeus.org>
@@ -41,13 +42,77 @@ class MongoDBTest extends TestCase
         $this->assertEquals($expected, $db->command(['listDatabases' => 1], [], $hash));
     }
 
+    public function testReadPreference()
+    {
+        $database = $this->getDatabase();
+        $this->assertSame(['type' => \MongoClient::RP_PRIMARY], $database->getReadPreference());
+
+        $this->assertTrue($database->setReadPreference(\MongoClient::RP_SECONDARY, ['a' => 'b']));
+        $this->assertSame(['type' => \MongoClient::RP_SECONDARY, 'tagsets' => ['a' => 'b']], $database->getReadPreference());
+
+        // Only way to check whether options are passed down is through debugInfo
+        $writeConcern = $database->getDb()->__debugInfo()['readPreference'];
+
+        $this->assertSame(ReadPreference::RP_SECONDARY, $writeConcern->getMode());
+        $this->assertSame(['a' => 'b'], $writeConcern->getTagSets());
+    }
+
+    public function testReadPreferenceIsInherited()
+    {
+        $client = $this->getClient();
+        $client->setReadPreference(\MongoClient::RP_SECONDARY, ['a' => 'b']);
+
+        $database = $client->selectDB('test');
+        $this->assertSame(['type' => \MongoClient::RP_SECONDARY, 'tagsets' => ['a' => 'b']], $database->getReadPreference());
+    }
+
+    public function testWriteConcern()
+    {
+        $database = $this->getDatabase();
+        $this->assertSame(['w' => 1, 'wtimeout' => 0], $database->getWriteConcern());
+        $this->assertSame(1, $database->w);
+        $this->assertSame(0, $database->wtimeout);
+
+        $this->assertTrue($database->setWriteConcern('majority', 100));
+        $this->assertSame(['w' => 'majority', 'wtimeout' => 100], $database->getWriteConcern());
+
+        $database->w = 2;
+        $this->assertSame(['w' => 2, 'wtimeout' => 100], $database->getWriteConcern());
+
+        $database->wtimeout = -1;
+        $this->assertSame(['w' => 2, 'wtimeout' => 0], $database->getWriteConcern());
+
+        // Only way to check whether options are passed down is through debugInfo
+        $writeConcern = $database->getDb()->__debugInfo()['writeConcern'];
+
+        $this->assertSame(2, $writeConcern->getW());
+        $this->assertSame(0, $writeConcern->getWtimeout());
+    }
+
+    public function testWriteConcernIsInherited()
+    {
+        $client = $this->getClient();
+        $client->setWriteConcern('majority', 100);
+
+        $database = $client->selectDB('test');
+        $this->assertSame(['w' => 'majority', 'wtimeout' => 100], $database->getWriteConcern());
+    }
+
     /**
      * @return \MongoDB
      */
     protected function getDatabase()
     {
-        $client = new \MongoClient();
+        $client = $this->getClient();
 
         return $client->selectDB('mongo-php-adapter');
     }
+
+    /**
+     * @return \MongoClient
+     */
+    protected function getClient()
+    {
+        return new \MongoClient();
+    }
 }