Kaynağa Gözat

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

Add helpers for readPreference and writeConcern
Andreas 10 yıl önce
ebeveyn
işleme
9578a49648

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

@@ -14,15 +14,18 @@
  */
  */
 
 
 namespace Alcaeus\MongoDbAdapter;
 namespace Alcaeus\MongoDbAdapter;
+
+use Alcaeus\MongoDbAdapter\Helper\ReadPreference;
 use MongoDB\Collection;
 use MongoDB\Collection;
 use MongoDB\Driver\Cursor;
 use MongoDB\Driver\Cursor;
-use MongoDB\Driver\ReadPreference;
 
 
 /**
 /**
  * @internal
  * @internal
  */
  */
 abstract class AbstractCursor
 abstract class AbstractCursor
 {
 {
+    use ReadPreference;
+
     /**
     /**
      * @var int
      * @var int
      */
      */
@@ -67,11 +70,6 @@ abstract class AbstractCursor
     ];
     ];
 
 
     /**
     /**
-     * @var array
-     */
-    protected $readPreference = [];
-
-    /**
      * @return Cursor
      * @return Cursor
      */
      */
     abstract protected function ensureCursor();
     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
      * @return array
      */
      */
     public function info()
     public function info()
@@ -213,27 +201,7 @@ abstract class AbstractCursor
      */
      */
     public function setReadPreference($readPreference, $tags = null)
     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;
         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
      * @return \IteratorIterator
      */
      */
     protected function ensureIterator()
     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.
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
  */
 
 
+use Alcaeus\MongoDbAdapter\Helper;
 use MongoDB\Client;
 use MongoDB\Client;
 
 
 /**
 /**
@@ -22,6 +23,9 @@ use MongoDB\Client;
  */
  */
 class MongoClient
 class MongoClient
 {
 {
+    use Helper\ReadPreference;
+    use Helper\WriteConcern;
+
     const VERSION = '1.6.12';
     const VERSION = '1.6.12';
     const DEFAULT_HOST = "localhost" ;
     const DEFAULT_HOST = "localhost" ;
     const DEFAULT_PORT = 27017 ;
     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
      * Kills a specific cursor on the server
      *
      *
      * @link http://www.php.net/manual/en/mongoclient.killcursor.php
      * @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)
     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.
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
  */
 
 
+use Alcaeus\MongoDbAdapter\Helper;
 use Alcaeus\MongoDbAdapter\TypeConverter;
 use Alcaeus\MongoDbAdapter\TypeConverter;
 
 
 /**
 /**
@@ -21,6 +22,9 @@ use Alcaeus\MongoDbAdapter\TypeConverter;
  */
  */
 class MongoCollection
 class MongoCollection
 {
 {
+    use Helper\ReadPreference;
+    use Helper\WriteConcern;
+
     const ASCENDING = 1;
     const ASCENDING = 1;
     const DESCENDING = -1;
     const DESCENDING = -1;
 
 
@@ -40,16 +44,6 @@ class MongoCollection
     protected $collection;
     protected $collection;
 
 
     /**
     /**
-     * @var int<p>
-     */
-    public $w;
-
-    /**
-     * @var int <p>
-     */
-    public $wtimeout;
-
-    /**
      * Creates a new collection
      * Creates a new collection
      * @link http://www.php.net/manual/en/mongocollection.construct.php
      * @link http://www.php.net/manual/en/mongocollection.construct.php
      * @param MongoDB $db Parent database.
      * @param MongoDB $db Parent database.
@@ -61,7 +55,11 @@ class MongoCollection
     {
     {
         $this->db = $db;
         $this->db = $db;
         $this->name = $name;
         $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)
     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);
         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
      * @link http://www.php.net/manual/en/mongocollection.aggregate.php
      * @param array $pipeline
      * @param array $pipeline
      * @param array $op
      * @param array $op
@@ -154,7 +169,10 @@ class MongoCollection
 
 
         $command += $options;
         $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())
     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');
         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();
         $this->errorIfOpened();
         static::$slaveOkay = $okay;
         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
      * @return Cursor
      */
      */
     protected function ensureCursor()
     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.
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
  */
 
 
+use Alcaeus\MongoDbAdapter\Helper;
 use MongoDB\Model\CollectionInfo;
 use MongoDB\Model\CollectionInfo;
 
 
 /**
 /**
@@ -21,24 +22,27 @@ use MongoDB\Model\CollectionInfo;
  */
  */
 class MongoDB
 class MongoDB
 {
 {
+    use Helper\ReadPreference;
+    use Helper\WriteConcern;
+
     const PROFILING_OFF = 0;
     const PROFILING_OFF = 0;
     const PROFILING_SLOW = 1;
     const PROFILING_SLOW = 1;
     const PROFILING_ON = 2;
     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
      * Creates a new database
@@ -50,11 +54,15 @@ class MongoDB
      * @throws Exception
      * @throws Exception
      * @return MongoDB Returns the database.
      * @return MongoDB Returns the database.
      */
      */
-    public function __construct($conn, $name)
+    public function __construct(MongoClient $conn, $name)
     {
     {
         $this->connection = $conn;
         $this->connection = $conn;
         $this->name = $name;
         $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
      * Gets a collection
+     *
      * @link http://www.php.net/manual/en/mongodb.get.php
      * @link http://www.php.net/manual/en/mongodb.get.php
      * @param string $name The name of the collection.
      * @param string $name The name of the collection.
      * @return MongoCollection
      * @return MongoCollection
      */
      */
     public function __get($name)
     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);
         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/>
      * (PECL mongo &gt;= 1.3.0)<br/>
      * @link http://www.php.net/manual/en/mongodb.getcollectionnames.php
      * @link http://www.php.net/manual/en/mongodb.getcollectionnames.php
      * Get all collections from this database
      * 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/>
      * (PECL mongo &gt;= 0.9.3)<br/>
      * Runs JavaScript code on the database server.
      * Runs JavaScript code on the database server.
      * @link http://www.php.net/manual/en/mongodb.execute.php
      * @link http://www.php.net/manual/en/mongodb.execute.php
@@ -381,6 +393,7 @@ class MongoDB
     {
     {
         try {
         try {
             $cursor = new \MongoCommandCursor($this->connection, $this->name, $data);
             $cursor = new \MongoCommandCursor($this->connection, $this->name, $data);
+            $cursor->setReadPreference($this->getReadPreference());
 
 
             return iterator_to_array($cursor)[0];
             return iterator_to_array($cursor)[0];
         } catch (\MongoDB\Driver\Exception\RuntimeException $e) {
         } 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()
     protected function notImplemented()
     {
     {
         throw new \Exception('Not implemented');
         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 $objectID;
 
 
-    private $attributes = [];
-
     /**
     /**
      * Creates a new id
      * Creates a new id
      *
      *
@@ -88,7 +86,7 @@ class MongoId implements Serializable, TypeInterface
             return (string) $this->objectID;
             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);
             trigger_error("The '\$id' property is read-only", E_DEPRECATED);
             return;
             return;
         }
         }
-
-        $this->attributes[$name] = $value;
     }
     }
 
 
     /**
     /**
@@ -111,7 +107,7 @@ class MongoId implements Serializable, TypeInterface
      */
      */
     public function __isset($name)
     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);
             trigger_error("The '\$id' property is read-only", E_DEPRECATED);
             return;
             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);
         $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
      * @param array|null $options
      * @return \MongoClient
      * @return \MongoClient

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

@@ -1,6 +1,7 @@
 <?php
 <?php
 
 
 namespace Alcaeus\MongoDbAdapter\Tests;
 namespace Alcaeus\MongoDbAdapter\Tests;
+use MongoDB\Driver\ReadPreference;
 
 
 /**
 /**
  * @author alcaeus <alcaeus@alcaeus.org>
  * @author alcaeus <alcaeus@alcaeus.org>
@@ -136,14 +137,78 @@ class MongoCollectionTest extends TestCase
         ], iterator_to_array($cursor));
         ], 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
      * @return \MongoCollection
      */
      */
     protected function getCollection($name = 'test')
     protected function getCollection($name = 'test')
     {
     {
+        return $this->getDatabase()->selectCollection($name);
+    }
+
+    /**
+     * @return \MongoDB
+     */
+    protected function getDatabase()
+    {
         $client = new \MongoClient();
         $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());
         $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
      * @param string $name
      * @return \MongoCollection
      * @return \MongoCollection

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

@@ -1,6 +1,7 @@
 <?php
 <?php
 
 
 namespace Alcaeus\MongoDbAdapter\Tests;
 namespace Alcaeus\MongoDbAdapter\Tests;
+use MongoDB\Driver\ReadPreference;
 
 
 /**
 /**
  * @author alcaeus <alcaeus@alcaeus.org>
  * @author alcaeus <alcaeus@alcaeus.org>
@@ -41,13 +42,77 @@ class MongoDBTest extends TestCase
         $this->assertEquals($expected, $db->command(['listDatabases' => 1], [], $hash));
         $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
      * @return \MongoDB
      */
      */
     protected function getDatabase()
     protected function getDatabase()
     {
     {
-        $client = new \MongoClient();
+        $client = $this->getClient();
 
 
         return $client->selectDB('mongo-php-adapter');
         return $client->selectDB('mongo-php-adapter');
     }
     }
+
+    /**
+     * @return \MongoClient
+     */
+    protected function getClient()
+    {
+        return new \MongoClient();
+    }
 }
 }