Completed
Pull Request — master (#28)
by Andreas
03:06
created

MongoWriteBatch::validate()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 22
Code Lines 14

Duplication

Lines 16
Ratio 72.73 %
Metric Value
dl 16
loc 22
rs 6.9811
cc 7
eloc 14
nc 7
nop 1
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 */
15
16
use Alcaeus\MongoDbAdapter\TypeConverter;
17
use Alcaeus\MongoDbAdapter\Helper\WriteConcernConverter;
18
19
/**
20
 * MongoWriteBatch allows you to "batch up" multiple operations (of same type)
21
 * and shipping them all to MongoDB at the same time. This can be especially
22
 * useful when operating on many documents at the same time to reduce roundtrips.
23
 *
24
 * @see http://php.net/manual/en/class.mongowritebatch.php
25
 */
26
class MongoWriteBatch
27
{
28
    use WriteConcernConverter;
29
30
    const COMMAND_INSERT = 1;
31
    const COMMAND_UPDATE = 2;
32
    const COMMAND_DELETE = 3;
33
34
    /**
35
     * @var MongoCollection
36
     */
37
    private $collection;
38
39
    /**
40
     * @var int
41
     */
42
    private $batchType;
43
44
    /**
45
     * @var array
46
     */
47
    private $writeOptions;
48
49
    /**
50
     * @var array
51
     */
52
    private $items = [];
53
54
    /**
55
     * Creates a new batch of write operations
56
     *
57
     * @see http://php.net/manual/en/mongowritebatch.construct.php
58
     * @param MongoCollection $collection
59
     * @param int $batchType
60
     * @param array $writeOptions
61
     */
62
    protected function __construct(MongoCollection $collection, $batchType, $writeOptions)
63
    {
64
        $this->collection = $collection;
65
        $this->batchType = $batchType;
66
        $this->writeOptions = $writeOptions;
67
    }
68
69
    /**
70
     * Adds a write operation to a batch
71
     *
72
     * @see http://php.net/manual/en/mongowritebatch.add.php
73
     * @param array|object $item
74
     * @return boolean
75
     */
76
    public function add($item)
77
    {
78
        if (is_object($item)) {
79
            $item = (array)$item;
80
        }
81
82
        $this->validate($item);
83
        $this->addItem($item);
84
85
        return true;
86
    }
87
88
    /**
89
     * Executes a batch of write operations
90
     *
91
     * @see http://php.net/manual/en/mongowritebatch.execute.php
92
     * @param array $writeOptions
93
     * @return array
94
     */
95
    final public function execute(array $writeOptions = [])
96
    {
97
        $writeOptions += $this->writeOptions;
98
        if (! count($this->items)) {
99
            return ['ok' => true];
100
        }
101
102
        if (isset($writeOptions['j'])) {
103
            trigger_error('j parameter is not supported', E_WARNING);
104
        }
105
        if (isset($writeOptions['fsync'])) {
106
            trigger_error('fsync parameter is not supported', E_WARNING);
107
        }
108
109
        $options['writeConcern'] = $this->createWriteConcernFromArray($writeOptions);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$options was never initialized. Although not strictly required by PHP, it is generally a good practice to add $options = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
110
        if (isset($writeOptions['ordered'])) {
111
            $options['ordered'] = $writeOptions['ordered'];
112
        }
113
114
        $collection = $this->collection->getCollection();
115
116
        try {
117
            $result = $collection->BulkWrite($this->items, $options);
118
            $ok = true;
119
        } catch (\MongoDB\Driver\Exception\BulkWriteException $e) {
120
            $result = $e->getWriteResult();
121
            $ok = false;
122
        }
123
124
        if ($ok === true) {
125
            $this->items = [];
126
        }
127
128
        switch ($this->batchType) {
129
            case self::COMMAND_UPDATE:
130
                $upsertedIds = [];
131
                foreach ($result->getUpsertedIds() as $index => $id) {
132
                    $upsertedIds[] = [
133
                        'index' => $index,
134
                        '_id' => TypeConverter::toLegacy($id)
135
                    ];
136
                }
137
138
                $result = [
139
                    'nMatched' => $result->getMatchedCount(),
140
                    'nModified' => $result->getModifiedCount(),
141
                    'nUpserted' => $result->getUpsertedCount(),
142
                    'ok' => $ok,
143
                ];
144
145
                if (count($upsertedIds)) {
146
                    $result['upserted'] = $upsertedIds;
147
                }
148
149
                return $result;
150
151
            case self::COMMAND_DELETE:
152
                return [
153
                    'nRemoved' => $result->getDeletedCount(),
154
                    'ok' => $ok,
155
                ];
156
157
            case self::COMMAND_INSERT:
158
                return [
159
                    'nInserted' => $result->getInsertedCount(),
160
                    'ok' => $ok,
161
                ];
162
        }
163
    }
164
165
    private function validate(array $item)
166
    {
167
        switch ($this->batchType) {
168 View Code Duplication
            case self::COMMAND_UPDATE:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
169
                if (! isset($item['q'])) {
170
                    throw new Exception("Expected \$item to contain 'q' key");
171
                }
172
                if (! isset($item['u'])) {
173
                    throw new Exception("Expected \$item to contain 'u' key");
174
                }
175
                break;
176
177 View Code Duplication
            case self::COMMAND_DELETE:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
178
                if (! isset($item['q'])) {
179
                    throw new Exception("Expected \$item to contain 'q' key");
180
                }
181
                if (! isset($item['limit'])) {
182
                    throw new Exception("Expected \$item to contain 'limit' key");
183
                }
184
                break;
185
        }
186
    }
187
188
    private function addItem(array $item)
189
    {
190
        switch ($this->batchType) {
191
            case self::COMMAND_UPDATE:
192
                $method = isset($item['multi']) ? 'updateMany' : 'updateOne';
193
194
                $options = [];
195
                if (isset($item['upsert']) && $item['upsert']) {
196
                    $options['upsert'] = true;
197
                }
198
199
                $this->items[] = [$method => [TypeConverter::fromLegacy($item['q']), TypeConverter::fromLegacy($item['u']), $options]];
200
                break;
201
202
            case self::COMMAND_INSERT:
203
                $this->items[] = ['insertOne' => [TypeConverter::fromLegacy($item)]];
204
                break;
205
206
            case self::COMMAND_DELETE:
207
                $method = $item['limit'] === 0 ? 'deleteMany' : 'deleteOne';
208
209
                $this->items[] = [$method => [TypeConverter::fromLegacy($item['q'])]];
210
                break;
211
        }
212
    }
213
}
214