Passed
Push — master ( 57b2d8...55b64c )
by Raffael
22:23 queued 13s
created

Factory::watchFrom()   B

Complexity

Conditions 10
Paths 24

Size

Total Lines 48

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 110

Importance

Changes 0
Metric Value
dl 0
loc 48
ccs 0
cts 30
cp 0
rs 7.2678
c 0
b 0
f 0
cc 10
nc 24
nop 8
crap 110

How to fix   Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * tubee
7
 *
8
 * @copyright   Copryright (c) 2017-2019 gyselroth GmbH (https://gyselroth.com)
9
 * @license     GPL-3.0 https://opensource.org/licenses/GPL-3.0
10
 */
11
12
namespace Tubee\Resource;
13
14
use Closure;
15
use Garden\Schema\ArrayRefLookup;
16
use Garden\Schema\Schema;
17
use Generator;
18
use InvalidArgumentException;
19
use MongoDB\BSON\ObjectId;
20
use MongoDB\BSON\ObjectIdInterface;
21
use MongoDB\BSON\UTCDateTime;
22
use MongoDB\Collection;
23
use MongoDB\Database;
24
use Psr\Log\LoggerInterface;
25
use Psr\SimpleCache\CacheInterface;
26
use Symfony\Component\Yaml\Yaml;
27
28
class Factory
29
{
30
    /**
31
     * OpenAPI.
32
     */
33
    const SPEC = __DIR__.'/../Rest/v1/openapi.yml';
34
35
    /**
36
     * Logger.
37
     *
38
     * @var LoggerInterface
39
     */
40
    protected $logger;
41
42
    /**
43
     * Database.
44
     *
45
     * @var Database
46
     */
47
    protected $db;
48
49
    /**
50
     * Cache.
51
     *
52
     * @var CacheInterface
53
     */
54
    protected $cache;
55
56
    /**
57
     * Initialize.
58
     */
59 21
    public function __construct(LoggerInterface $logger, CacheInterface $cache)
60
    {
61 21
        $this->logger = $logger;
62 21
        $this->cache = $cache;
63 21
    }
64
65
    /**
66
     * Get resource schema.
67
     */
68 15
    public function getSchema(string $kind): Schema
69
    {
70 15
        if ($this->cache->has($kind)) {
71
            //    return $this->cache->get($kind);
72
        }
73
74 15
        $spec = $this->loadSpecification();
75
76 15
        if (!isset($spec['components']['schemas'][$kind])) {
77
            throw new InvalidArgumentException('Provided resource kind is invalid');
78
        }
79
80 15
        $schema = new Schema($spec['components']['schemas'][$kind]);
81 15
        $schema->setRefLookup(new ArrayRefLookup($spec));
82 15
        $schema->setFlags(Schema::VALIDATE_EXTRA_PROPERTY_EXCEPTION);
83 15
        $this->cache->set($kind, $schema);
84
85 15
        return $schema;
86
    }
87
88
    /**
89
     * Validate resource.
90
     */
91 15
    public function validate(array $resource): array
92
    {
93 15
        $this->logger->debug('validate resource ['.$resource['kind'].'] against schema', [
94 15
            'category' => get_class($this),
95
        ]);
96
97 15
        return $this->getSchema($resource['kind'])->validate($resource);
98
    }
99
100
    /**
101
     * Add resource.
102
     */
103 15
    public function addTo(Collection $collection, array $resource, bool $simulate = false): ObjectIdInterface
104
    {
105 15
        $ts = new UTCDateTime();
106
        $resource += [
107 15
            'created' => $ts,
108 15
            'changed' => $ts,
109 15
            'version' => 1,
110
        ];
111
112 15
        $this->logger->debug('add new resource to ['.$collection->getCollectionName().']', [
113 15
            'category' => get_class($this),
114 15
            'resource' => $resource,
115
        ]);
116
117 15
        if ($simulate === true) {
118
            return new ObjectId();
119
        }
120
121 15
        $result = $collection->insertOne($resource);
122 15
        $id = $result->getInsertedId();
123
124 15
        $this->logger->info('created new resource ['.$id.'] in ['.$collection->getCollectionName().']', [
125 15
            'category' => get_class($this),
126
        ]);
127
128 15
        return $id;
129
    }
130
131
    /**
132
     * Update resource.
133
     */
134
    public function updateIn(Collection $collection, ResourceInterface $resource, array $update, bool $simulate = false): bool
135
    {
136
        $this->logger->debug('update resource ['.$resource->getId().'] in ['.$collection->getCollectionName().']', [
137
            'category' => get_class($this),
138
            'update' => $update,
139
        ]);
140
141
        $op = [
142
            '$set' => $update,
143
        ];
144
145
        if (!isset($update['data']) || $resource->getData() === $update['data']) {
146
            $this->logger->info('resource ['.$resource->getId().'] version ['.$resource->getVersion().'] in ['.$collection->getCollectionName().'] is already up2date', [
147
                'category' => get_class($this),
148
            ]);
149
        } else {
150
            $this->logger->info('add new history record for resource ['.$resource->getId().'] in ['.$collection->getCollectionName().']', [
151
                'category' => get_class($this),
152
            ]);
153
154
            $op['$set']['changed'] = new UTCDateTime();
155
            $op += [
156
                '$addToSet' => ['history' => array_intersect_key($resource->toArray(), array_flip(['data', 'version', 'changed', 'description']))],
157
                '$inc' => ['version' => 1],
158
            ];
159
        }
160
161
        if ($simulate === true) {
162
            return true;
163
        }
164
165
        $result = $collection->updateOne(['_id' => $resource->getId()], $op);
0 ignored issues
show
Unused Code introduced by
$result is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
166
167
        $this->logger->info('updated resource ['.$resource->getId().'] in ['.$collection->getCollectionName().']', [
168
            'category' => get_class($this),
169
        ]);
170
171
        return true;
172
    }
173
174
    /**
175
     * Delete resource.
176
     */
177 1
    public function deleteFrom(Collection $collection, ObjectIdInterface $id, bool $simulate = false): bool
178
    {
179 1
        $this->logger->info('delete resource ['.$id.'] from ['.$collection->getCollectionName().']', [
180 1
            'category' => get_class($this),
181
        ]);
182
183 1
        if ($simulate === true) {
184
            return true;
185
        }
186
187 1
        $result = $collection->deleteOne(['_id' => $id]);
0 ignored issues
show
Unused Code introduced by
$result is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
188
189 1
        return true;
190
    }
191
192
    /**
193
     * Get all.
194
     */
195 6
    public function getAllFrom(Collection $collection, ?array $query = null, ?int $offset = null, ?int $limit = null, ?array $sort = null, ?Closure $build = null): Generator
196
    {
197 6
        $total = $collection->count($query);
0 ignored issues
show
Deprecated Code introduced by
The method MongoDB\Collection::count() has been deprecated with message: 1.4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
198 6
        $offset = $this->calcOffset($total, $offset);
199
200 6
        if (empty($sort)) {
201 6
            $sort = ['$natural' => -1];
202
        }
203
204 6
        $result = $collection->find($query, [
205 6
            'projection' => ['history' => 0],
206 6
            'skip' => $offset,
207 6
            'limit' => $limit,
208 6
            'sort' => $sort,
209
        ]);
210
211 6
        foreach ($result as $resource) {
212 6
            yield (string) $resource['_id'] => $build->call($this, $resource);
213
        }
214
215 6
        return $total;
216
    }
217
218
    /**
219
     * Change stream.
220
     */
221
    public function watchFrom(Collection $collection, ?ObjectIdInterface $after = null, bool $existing = true, ?array $query = [], ?Closure $build = null, ?int $offset = null, ?int $limit = null, ?array $sort = null): Generator
222
    {
223
        $pipeline = $query;
224
        if (!empty($pipeline)) {
225
            $pipeline = [['$match' => $query]];
226
        }
227
228
        $stream = $collection->watch($pipeline, [
229
            'resumeAfter' => $after,
230
        ]);
231
232
        if ($existing === true) {
233
            $query = $this->prepareQuery($query);
234
            $total = $collection->count($query);
0 ignored issues
show
Deprecated Code introduced by
The method MongoDB\Collection::count() has been deprecated with message: 1.4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
235
236
            if ($offset !== null && $total === 0) {
237
                $offset = null;
238
            } elseif ($offset < 0 && $total >= $offset * -1) {
239
                $offset = $total + $offset;
240
            }
241
242
            $result = $collection->find($query, [
243
                'projection' => ['history' => 0],
244
                'skip' => $offset,
245
                'limit' => $limit,
246
                'sort' => $sort,
247
            ]);
248
249
            foreach ($result as $resource) {
250
                yield (string) $resource['_id'] => [
251
                    'insert',
252
                    $build->call($this, $resource),
253
                ];
254
            }
255
        }
256
257
        for ($stream->rewind(); true; $stream->next()) {
258
            if (!$stream->valid()) {
259
                continue;
260
            }
261
262
            $event = $stream->current();
263
            yield (string) $event['fullDocument']['_id'] => [
264
                $event['operationType'],
265
                $build->call($this, $event['fullDocument']),
266
            ];
267
        }
268
    }
269
270
    /**
271
     * Build.
272
     */
273 9
    public function initResource(ResourceInterface $resource)
274
    {
275 9
        $this->logger->debug('initialized resource ['.$resource->getId().'] as ['.get_class($resource).']', [
276 9
            'category' => get_class($this),
277
        ]);
278
279 9
        return $resource;
280
    }
281
282
    /**
283
     * Load openapi specs.
284
     */
285 15
    protected function loadSpecification(): array
286
    {
287 15
        if ($this->cache->has('openapi')) {
288
            //return $this->cache->get('openapi');
289
        }
290
291 15
        $data = Yaml::parseFile(self::SPEC);
292 15
        $this->cache->set('openapi', $data);
293
294 15
        return $data;
295
    }
296
297
    /**
298
     * Remove fullDocument prefix from keys.
299
     */
300
    protected function prepareQuery(array $query): array
301
    {
302
        $filter = $query;
303
        if (isset($query['$and'])) {
304
            $query = $query['$and'][0];
305
        }
306
307
        $new = [];
308
        foreach ($query as $key => $value) {
309
            $new[substr($key, 13)] = $value;
310
        }
311
312
        if (isset($filter['$and'])) {
313
            $filter['$and'][0] = $new;
314
315
            return $filter;
316
        }
317
318
        return $new;
319
    }
320
321
    /**
322
     * Calculate offset.
323
     */
324 6
    protected function calcOffset(int $total, ?int $offset = null): ?int
325
    {
326 6
        if ($offset !== null && $total === 0) {
327
            $offset = 0;
328 6
        } elseif ($offset < 0 && $total >= $offset * -1) {
329
            $offset = $total + $offset;
330 6
        } elseif ($offset < 0) {
331
            $offset = 0;
332
        }
333
334 6
        return $offset;
335
    }
336
}
337