Completed
Push — master ( ae0103...9b98c5 )
by Joao
04:23 queued 56s
created

MongoDbDriver::deleteDocuments()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 0
cts 14
cp 0
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 10
nc 2
nop 2
crap 6
1
<?php
2
3
namespace ByJG\AnyDataset\Store;
4
5
use ByJG\AnyDataset\Dataset\IteratorFilter;
6
use ByJG\AnyDataset\Enum\Relation;
7
use ByJG\AnyDataset\NoSqlDocument;
8
use ByJG\AnyDataset\NoSqlInterface;
9
use ByJG\Serializer\BinderObject;
10
use ByJG\Util\Uri;
11
use MongoDB\BSON\Binary;
12
use MongoDB\BSON\Decimal128;
13
use MongoDB\BSON\Javascript;
14
use MongoDB\BSON\ObjectID;
15
use MongoDB\BSON\Timestamp;
16
use MongoDB\BSON\UTCDateTime;
17
use MongoDB\Driver\BulkWrite;
18
use MongoDB\Driver\Manager;
19
use MongoDB\Driver\Query;
20
use MongoDB\Driver\WriteConcern;
21
22
class MongoDbDriver implements NoSqlInterface
23
{
24
    const MONGO_DOCUMENT = [
25
        Binary::class,
26
        Decimal128::class,
27
        Javascript::class,
28
        ObjectID::class,
29
        Timestamp::class,
30
        UTCDateTime::class,
31
    ];
32
33
    /**
34
     *
35
     * @var Manager;
36
     */
37
    protected $mongoManager = null;
38
39
    /**
40
     * Enter description here...
41
     *
42
     * @var Uri
43
     */
44
    protected $connectionUri;
45
46
    protected $database;
47
48
    /**
49
     * Creates a new MongoDB connection.
50
     *
51
     *  mongodb://username:passwortd@host:port/database
52
     *
53
     * @param Uri $connUri
54
     */
55
    public function __construct(Uri $connUri)
56
    {
57
        $this->connectionUri = $connUri;
58
59
        $hosts = $this->connectionUri->getHost();
60
        $port = $this->connectionUri->getPort() == '' ? 27017 : $this->connectionUri->getPort();
61
        $path = preg_replace('~^/~', '', $this->connectionUri->getPath());
62
        $database = $path;
63
        $username = $this->connectionUri->getUsername();
64
        $password = $this->connectionUri->getPassword();
65
66
        if ($username != '' && $password != '') {
67
            $auth = array('username' => $username, 'password' => $password, 'connect' => 'true');
68
        } else {
69
            $auth = array('connect' => 'true');
70
        }
71
72
        $connectString = sprintf('mongodb://%s:%d', $hosts, $port);
73
        $this->mongoManager = new Manager($connectString, $auth);
74
        $this->database = $database;
75
    }
76
77
    /**
78
     * Closes and destruct the MongoDB connection
79
     */
80
    public function __destruct()
81
    {
82
        // $this->mongoManager->
83
    }
84
85
    /**
86
     * Gets the instance of MongoDB; You do not need uses this directly.
87
     * If you have to, probably something is missing in this class
88
     * @return Manager
89
     */
90
    public function getDbConnection()
91
    {
92
        return $this->mongoManager;
93
    }
94
95
    /**
96
     * @param $idDocument
97
     * @param null $collection
98
     * @return \ByJG\AnyDataset\NoSqlDocument|null
99
     */
100
    public function getDocumentById($idDocument, $collection = null)
101
    {
102
        $filter = new IteratorFilter();
103
        $filter->addRelation('_id', Relation::EQUAL, $idDocument);
104
        $document = $this->getDocuments($filter, $collection);
105
106
        if (empty($document)) {
107
            return null;
108
        }
109
110
        return $document[0];
111
    }
112
113
    /**
114
     * @param \ByJG\AnyDataset\Dataset\IteratorFilter $filter
115
     * @param null $collection
116
     * @return \ByJG\AnyDataset\NoSqlDocument[]|null
117
     */
118
    public function getDocuments(IteratorFilter $filter, $collection = null)
119
    {
120
        if (empty($collection)) {
121
            throw new \InvalidArgumentException('Collection is mandatory for MongoDB');
122
        }
123
124
        $dataCursor = $this->mongoManager->executeQuery(
125
            $this->database . '.' . $collection,
126
            $this->getMongoFilterArray($filter)
127
        );
128
129
        if (empty($dataCursor)) {
130
            return null;
131
        }
132
133
        $data = $dataCursor->toArray();
134
135
        $result = [];
136
        foreach ($data as $item) {
137
            $result[] = new NoSqlDocument(
138
                $item->_id,
139
                $collection,
140
                BinderObject::toArrayFrom($item, false, self::MONGO_DOCUMENT)
141
            );
142
        }
143
144
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $result; (array) is incompatible with the return type declared by the interface ByJG\AnyDataset\NoSqlInterface::getDocuments of type ByJG\AnyDataset\NoSqlDocument[]|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
145
    }
146
147
    protected function getMongoFilterArray(IteratorFilter $filter)
148
    {
149
        $result = [];
150
151
        foreach ($filter->getRawFilters() as $itemFilter) {
152
            $name = $itemFilter[1];
153
            $relation = $itemFilter[2];
154
            $value = $itemFilter[3];
155
156
            if ($itemFilter[0] == ' or ') {
157
                throw new \InvalidArgumentException('MongoDBDriver does not support the addRelationOr');
158
            }
159
160
            if (isset($result[$name])) {
161
                throw new \InvalidArgumentException('MongoDBDriver does not support filtering the same field twice');
162
            }
163
164
            switch ($relation) {
165
                case Relation::EQUAL:
166
                    $result[$name] = $value;
167
                    break;
168
169
                case Relation::GREATER_THAN:
170
                    $result[$name] = [ '$gt' => $value ];
171
                    break;
172
173
                case Relation::LESS_THAN:
174
                    $result[$name] = [ '$lt' => $value ];
175
                    break;
176
177
                case Relation::GREATER_OR_EQUAL_THAN:
178
                    $result[$name] = [ '$gte' => $value ];
179
                    break;
180
181
                case Relation::LESS_OR_EQUAL_THAN:
182
                    $result[$name] = [ '$lte' => $value ];
183
                    break;
184
185
                case Relation::NOT_EQUAL:
186
                    $result[$name] = [ '$ne' => $value ];
187
                    break;
188
189
                case Relation::STARTS_WITH:
190
                    $result[$name] = [ '$regex' => "^$value" ];
191
                    break;
192
193
                case Relation::CONTAINS:
194
                    $result[$name] = [ '$regex' => "$value" ];
195
                    break;
196
197
            }
198
        }
199
200
        return new Query($result);
201
    }
202
203
    public function deleteDocumentById($idDocument, $collection = null)
204
    {
205
        $filter = new IteratorFilter();
206
        $filter->addRelation('_id', Relation::EQUAL, $idDocument);
207
        $this->deleteDocuments($filter, $collection);
208
    }
209
210
211
    public function deleteDocuments(IteratorFilter $filter, $collection = null)
212
    {
213
        if (empty($collection)) {
214
            throw new \InvalidArgumentException('Collection is mandatory for MongoDB');
215
        }
216
217
        $writeConcern = new WriteConcern(WriteConcern::MAJORITY, 100);
218
        $bulkWrite = new BulkWrite();
219
220
        $bulkWrite->delete($this->getMongoFilterArray($filter));
221
        $this->mongoManager->executeBulkWrite(
222
            $this->database . '.' . $collection,
223
            $bulkWrite,
224
            $writeConcern
225
        );
226
    }
227
228
    public function save(NoSqlDocument $document)
229
    {
230
        if (empty($document->getCollection())) {
231
            throw new \InvalidArgumentException('Collection is mandatory for MongoDB');
232
        }
233
234
        $writeConcern = new WriteConcern(WriteConcern::MAJORITY, 100);
235
        $bulkWrite = new BulkWrite();
236
237
        $data = BinderObject::toArrayFrom($document->getDocument(), false, self::MONGO_DOCUMENT);
238
239
        $idDocument = $document->getIdDocument();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $idDocument is correct as $document->getIdDocument() (which targets ByJG\AnyDataset\NoSqlDocument::getIdDocument()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
240
        if (empty($idDocument)) {
241
            $idDocument = isset($data['_id']) ? $data['_id'] : null;
242
        }
243
244
        $data['updated'] = new UTCDateTime((new \DateTime())->getTimestamp()*1000);
245
        if (empty($idDocument)) {
246
            $data['_id'] = $idDocument = new ObjectID();
247
            $data['created'] = new UTCDateTime((new \DateTime())->getTimestamp()*1000);
248
            $bulkWrite->insert($data);
249
        } else {
250
            $data['_id'] = $idDocument;
251
            $bulkWrite->update(['_id' => $idDocument], ["\$set" => $data]);
252
        }
253
254
        $this->mongoManager->executeBulkWrite(
255
            $this->database . "." . $document->getCollection(),
256
            $bulkWrite,
257
            $writeConcern
258
        );
259
260
        $document->setDocument($data);
261
        $document->setIdDocument($idDocument);
262
263
        return $document;
264
    }
265
}
266