Completed
Push — develop ( 23bc41...c0c47b )
by Siad
04:08
created

CouchbaseResourceManager   C

Complexity

Total Complexity 65

Size/Duplication

Total Lines 518
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 33.98%

Importance

Changes 26
Bugs 9 Features 11
Metric Value
wmc 65
c 26
b 9
f 11
lcom 1
cbo 3
dl 0
loc 518
ccs 70
cts 206
cp 0.3398
rs 5.7895

21 Methods

Rating   Name   Duplication   Size   Complexity  
A getServer() 0 12 3
C normalizeServer() 0 44 13
A hasResource() 0 4 1
A getResource() 0 23 3
B setResource() 0 26 4
A normalizeLibOptions() 0 16 4
A normalizeLibOptionKey() 0 13 3
A setLibOptions() 0 15 2
B getLibOptions() 0 23 5
A removeResource() 0 6 1
A setServer() 0 13 2
B addServers() 0 25 4
A addServer() 0 4 1
A setUsername() 0 13 2
A getUsername() 0 10 2
A setBucket() 0 13 2
A getBucket() 0 10 2
A setPassword() 0 13 2
A getPassword() 0 10 2
A normalizeServers() 0 13 4
A compareServers() 0 10 3

How to fix   Complexity   

Complex Class

Complex classes like CouchbaseResourceManager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CouchbaseResourceManager, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * zf-couchbase2.
4
 *
5
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
6
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
7
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
8
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
9
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
10
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
11
 * SOFTWARE.
12
 *
13
 * @copyright 2015 MehrAlsNix (http://www.mehralsnix.de)
14
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
15
 *
16
 * @link      http://github.com/MehrAlsNix/zf-couchbase2
17
 */
18
namespace MehrAlsNix\ZF\Cache\Storage\Adapter;
19
20
use CouchbaseBucket as CouchbaseBucketResource;
21
use CouchbaseCluster as CouchbaseClusterResource;
22
use Zend\Cache\Exception;
23
use Zend\Stdlib\ArrayUtils;
24
25
/**
26
 * Class CouchbaseResourceManager.
27
 */
28
class CouchbaseResourceManager
0 ignored issues
show
Complexity introduced by
This class has a complexity of 65 which exceeds the configured maximum of 50.

The class complexity is the sum of the complexity of all methods. A very high value is usually an indication that your class does not follow the single reponsibility principle and does more than one job.

Some resources for further reading:

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

Loading history...
29
{
30
    /**
31
     * Registered resources.
32
     *
33
     * @var array
34
     */
35
    protected $resources = [];
36
37
    /**
38
     * Get servers.
39
     *
40
     * @param string $id
41
     *
42
     * @throws Exception\RuntimeException
43
     *
44
     * @return array array('host' => <host>, 'port' => <port>)
45
     */
46
    public function getServer($id)
47
    {
48
        if (!$this->hasResource($id)) {
49
            throw new Exception\RuntimeException("No resource with id '{$id}'");
50
        }
51
        $resource = &$this->resources[$id];
52
        if ($resource instanceof CouchbaseBucketResource) {
53
            return $resource->manager()->info()['hostname'];
54
        }
55
56
        return $resource['server'];
57
    }
58
59
    /**
60
     * Normalize one server into the following format:
61
     * array('host' => <host>, 'port' => <port>, 'weight' => <weight>).
62
     *
63
     * @param string|array &$server
64
     *
65
     * @throws Exception\InvalidArgumentException
66
     * @throws \Zend\Stdlib\Exception\InvalidArgumentException
67
     */
68 61
    protected function normalizeServer(&$server)
0 ignored issues
show
Complexity introduced by
This operation has 328 execution paths which exceeds the configured maximum of 200.

A high number of execution paths generally suggests many nested conditional statements and make the code less readible. This can usually be fixed by splitting the method into several smaller methods.

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

Loading history...
69
    {
70 61
        $host = null;
71 61
        $port = 8091;
72
        // convert a single server into an array
73 61
        if ($server instanceof \Traversable) {
74
            $server = ArrayUtils::iteratorToArray($server);
75
        }
76 61
        if (is_array($server)) {
77
            // array(<host>[, <port>])
78 61
            if (isset($server[0])) {
79 61
                $host = (string) $server[0];
80 61
                $port = isset($server[1]) ? (int) $server[1] : $port;
81 61
            }
82
            // array('host' => <host>[, 'port' => <port>])
83 61
            if (!isset($server[0]) && isset($server['host'])) {
84
                $host = (string) $server['host'];
85
                $port = isset($server['port']) ? (int) $server['port'] : $port;
86
            }
87 61
        } else {
88
            // parse server from URI host{:?port}
89
            $server = trim($server);
90
            if (strpos($server, '://') === false) {
91
                $server = 'http://'.$server;
92
            }
93
            $server = parse_url($server);
94
            if (!$server) {
95
                throw new Exception\InvalidArgumentException('Invalid server given');
96
            }
97
            $host = $server['host'];
98
            $port = isset($server['port']) ? (int) $server['port'] : $port;
99
            if (isset($server['query'])) {
100
                $query = null;
101
                parse_str($server['query'], $query);
102
            }
103
        }
104 61
        if (!$host) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $host of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
105
            throw new Exception\InvalidArgumentException('Missing required server host');
106
        }
107
        $server = [
108 61
            'host' => $host,
109 61
            'port' => $port,
110 61
        ];
111 61
    }
112
113
    /**
114
     * Check if a resource exists.
115
     *
116
     * @param string $id
117
     *
118
     * @return bool
119
     */
120 61
    public function hasResource($id)
121
    {
122 61
        return isset($this->resources[$id]);
123
    }
124
125
    /**
126
     * Gets a couchbase cluster resource.
127
     *
128
     * @param string $id
129
     *
130
     * @return \CouchbaseBucket
131
     *
132
     * @throws Exception\RuntimeException
133
     */
134 61
    public function getResource($id)
135
    {
136 61
        if (!$this->hasResource($id)) {
137
            throw new Exception\RuntimeException("No resource with id '{$id}'");
138
        }
139 61
        $resource = $this->resources[$id];
140 61
        if ($resource instanceof \CouchbaseBucket) {
141 61
            return $resource;
142
        }
143 61
        $memc = new CouchbaseClusterResource('couchbase://'.$resource['server'][0]['host'].':'.$resource['server'][0]['port'], (string) $resource['username'], (string) $resource['password']);
144 61
        $bucket = $memc->openBucket((string) $resource['bucket'], (string) $resource['password']);
145
        /*
146
        $bucket->setTranscoder(
147
            '\MehrAlsNix\ZF\Cache\Storage\Adapter\CouchbaseResourceManager::encoder',
148
            '\MehrAlsNix\ZF\Cache\Storage\Adapter\CouchbaseResourceManager::decoder'
149
        );
150
        */
151
152
        // buffer and return
153 61
        $this->resources[$id] = $bucket;
154
155 61
        return $bucket;
156
    }
157
158
    /**
159
     * Set a resource.
160
     *
161
     * @param string                                     $id
162
     * @param array|\Traversable|CouchbaseBucketResource $resource
163
     *
164
     * @return CouchbaseResourceManager Fluent interface
165
     *
166
     * @throws \Zend\Stdlib\Exception\InvalidArgumentException
167
     * @throws Exception\InvalidArgumentException
168
     */
169 61
    public function setResource($id, $resource)
170
    {
171 61
        $id = (string) $id;
172 61
        if (!($resource instanceof CouchbaseBucketResource)) {
173 61
            if ($resource instanceof \Traversable) {
174
                $resource = ArrayUtils::iteratorToArray($resource);
175 61
            } elseif (!is_array($resource)) {
176
                throw new Exception\InvalidArgumentException(
177
                    'Resource must be an instance of CouchbaseCluster or an array or Traversable'
178
                );
179
            }
180 61
            $resource = array_merge([
181 61
                'lib_options' => [],
182 61
                'server' => '',
183 61
                'username' => '',
184 61
                'password' => '',
185 61
                'bucket' => '',
186 61
            ], $resource);
187
            // normalize and validate params
188 61
            $this->normalizeServers($resource['server']);
189 61
            $this->normalizeLibOptions($resource['lib_options']);
190 61
        }
191 61
        $this->resources[$id] = $resource;
192
193 61
        return $this;
194
    }
195
196
    /**
197
     * Normalize libmemcached options.
198
     *
199
     * @param array|\Traversable $libOptions
200
     *
201
     * @throws Exception\InvalidArgumentException
202
     */
203 61
    protected function normalizeLibOptions(&$libOptions)
204
    {
205 61
        if (!is_array($libOptions) && !($libOptions instanceof \Traversable)) {
206
            throw new Exception\InvalidArgumentException(
207
                'Lib-Options must be an array or an instance of Traversable'
208
            );
209
        }
210
211 61
        $result = [];
212 61
        foreach ($libOptions as $key => $value) {
213
            $this->normalizeLibOptionKey($key);
214
            $result[$key] = $value;
215 61
        }
216
217 61
        $libOptions = $result;
218 61
    }
219
220
    /**
221
     * Convert option name into it's constant value.
222
     *
223
     * @param string|int $key
224
     *
225
     * @throws Exception\InvalidArgumentException
226
     */
227
    protected function normalizeLibOptionKey(&$key)
228
    {
229
        // convert option name into it's constant value
230
        if (is_string($key)) {
231
            $const = '\\COUCHBASE_SERTYPE_'.str_replace([' ', '-'], '_', strtoupper($key));
232
            if (!defined($const)) {
233
                throw new Exception\InvalidArgumentException("Unknown libcouchbase option '{$key}' ({$const})");
234
            }
235
            $key = constant($const);
236
        } else {
237
            $key = (int) $key;
238
        }
239
    }
240
241
    /**
242
     * Set Libmemcached options.
243
     *
244
     * @param string $id
245
     * @param array  $libOptions
246
     *
247
     * @return CouchbaseResourceManager Fluent interface
248
     */
249
    public function setLibOptions($id, array $libOptions)
250
    {
251
        if (!$this->hasResource($id)) {
252
            return $this->setResource($id, [
253
                'lib_options' => $libOptions,
254
            ]);
255
        }
256
257
        $this->normalizeLibOptions($libOptions);
258
259
        $resource = &$this->resources[$id];
260
        $resource['lib_options'] = $libOptions;
261
262
        return $this;
263
    }
264
265
    /**
266
     * Get Libmemcached options.
267
     *
268
     * @param string $id
269
     *
270
     * @return array
271
     *
272
     * @throws Exception\RuntimeException
273
     */
274
    public function getLibOptions($id)
275
    {
276
        if (!$this->hasResource($id)) {
277
            throw new Exception\RuntimeException("No resource with id '{$id}'");
278
        }
279
280
        $resource = &$this->resources[$id];
281
282
        if ($resource instanceof MemcachedResource) {
0 ignored issues
show
Bug introduced by
The class MehrAlsNix\ZF\Cache\Stor...apter\MemcachedResource does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
283
            $libOptions = [];
284
            $reflection = new ReflectionClass('Memcached');
285
            $constants = $reflection->getConstants();
286
            foreach ($constants as $constName => $constValue) {
287
                if (substr($constName, 0, 4) == 'OPT_') {
288
                    $libOptions[$constValue] = $resource->getOption($constValue);
289
                }
290
            }
291
292
            return $libOptions;
293
        }
294
295
        return $resource['lib_options'];
296
    }
297
298
    /**
299
     * Remove a resource.
300
     *
301
     * @param string $id
302
     *
303
     * @return CouchbaseResourceManager Fluent interface
304
     */
305
    public function removeResource($id)
306
    {
307
        unset($this->resources[$id]);
308
309
        return $this;
310
    }
311
312
    /**
313
     * Set servers.
314
     *
315
     * $servers can be an array list or a comma separated list of servers.
316
     * One server in the list can be descripted as follows:
317
     * - URI:   [tcp://]<host>[:<port>][?weight=<weight>]
318
     * - Assoc: array('host' => <host>[, 'port' => <port>][, 'weight' => <weight>])
319
     * - List:  array(<host>[, <port>][, <weight>])
320
     *
321
     * @param string $id
322
     * @param string $server
323
     *
324
     * @return CouchbaseResourceManager
325
     */
326 61
    public function setServer($id, $server)
327
    {
328 61
        if (!$this->hasResource($id)) {
329 61
            return $this->setResource($id, [
330 61
                'server' => $server,
331 61
            ]);
332
        }
333
        $this->normalizeServers($server);
334
        $resource = &$this->resources[$id];
335
        $resource['server'] = $server;
336
337
        return $this;
338
    }
339
340
    /**
341
     * Add servers.
342
     *
343
     * @param string       $id
344
     * @param string|array $servers
345
     *
346
     * @return CouchbaseResourceManager
347
     */
348
    public function addServers($id, $servers)
349
    {
350
        if (!$this->hasResource($id)) {
351
            return $this->setResource($id, [
352
                'servers' => $servers,
353
            ]);
354
        }
355
        $this->normalizeServers($servers);
356
        $resource = &$this->resources[$id];
357
        if ($resource instanceof CouchbaseBucketResource) {
358
            // don't add servers twice
359
            $servers = array_udiff($servers, $resource->getServerList(), [$this, 'compareServers']);
0 ignored issues
show
Bug introduced by
It seems like $servers can also be of type string; however, array_udiff() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
360
            if ($servers) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $servers of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
361
                $resource->addServers($servers);
362
            }
363
        } else {
364
            // don't add servers twice
365
            $resource['servers'] = array_merge(
366
                $resource['servers'],
367
                array_udiff($servers, $resource['servers'], [$this, 'compareServers'])
0 ignored issues
show
Bug introduced by
It seems like $servers defined by parameter $servers on line 348 can also be of type string; however, array_udiff() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
368
            );
369
        }
370
371
        return $this;
372
    }
373
374
    /**
375
     * Add one server.
376
     *
377
     * @param string       $id
378
     * @param string|array $server
379
     *
380
     * @return CouchbaseResourceManager
381
     */
382
    public function addServer($id, $server)
383
    {
384
        return $this->addServers($id, [$server]);
385
    }
386
387
    /**
388
     * Set servers.
389
     *
390
     * $servers can be an array list or a comma separated list of servers.
391
     * One server in the list can be descripted as follows:
392
     * - URI:   [tcp://]<host>[:<port>][?weight=<weight>]
393
     * - Assoc: array('host' => <host>[, 'port' => <port>][, 'weight' => <weight>])
394
     * - List:  array(<host>[, <port>][, <weight>])
395
     *
396
     * @param string $id
397
     * @param string $username
398
     *
399
     * @return CouchbaseResourceManager
400
     */
401
    public function setUsername($id, $username)
402
    {
403
        if (!$this->hasResource($id)) {
404
            return $this->setResource($id, [
405
                'username' => $username,
406
            ]);
407
        }
408
409
        $resource = &$this->resources[$id];
410
        $resource['username'] = $username;
411
412
        return $this;
413
    }
414
415
    public function getUsername($id)
416
    {
417
        if (!$this->hasResource($id)) {
418
            return $this->getResource($id);
419
        }
420
421
        $resource = &$this->resources[$id];
422
423
        return $resource['username'];
424
    }
425
426
    /**
427
     * Set servers.
428
     *
429
     * $servers can be an array list or a comma separated list of servers.
430
     * One server in the list can be descripted as follows:
431
     * - URI:   [tcp://]<host>[:<port>][?weight=<weight>]
432
     * - Assoc: array('host' => <host>[, 'port' => <port>][, 'weight' => <weight>])
433
     * - List:  array(<host>[, <port>][, <weight>])
434
     *
435
     * @param string $id
436
     * @param string $bucket
437
     *
438
     * @return CouchbaseResourceManager
439
     */
440 61
    public function setBucket($id, $bucket)
441
    {
442 61
        if (!$this->hasResource($id)) {
443
            return $this->setResource($id, [
444
                'bucket' => $bucket,
445
            ]);
446
        }
447
448 61
        $resource = &$this->resources[$id];
449 61
        $resource['bucket'] = $bucket;
450
451 61
        return $this;
452
    }
453
454
    public function getBucket($id)
455
    {
456
        if (!$this->hasResource($id)) {
457
            return $this->getResource($id);
458
        }
459
460
        $resource = &$this->resources[$id];
461
462
        return $resource['bucket'];
463
    }
464
465
    /**
466
     * Set servers.
467
     *
468
     * $servers can be an array list or a comma separated list of servers.
469
     * One server in the list can be descripted as follows:
470
     * - URI:   [tcp://]<host>[:<port>][?weight=<weight>]
471
     * - Assoc: array('host' => <host>[, 'port' => <port>][, 'weight' => <weight>])
472
     * - List:  array(<host>[, <port>][, <weight>])
473
     *
474
     * @param string $id
475
     * @param string $password
476
     *
477
     * @return CouchbaseResourceManager
478
     */
479
    public function setPassword($id, $password)
480
    {
481
        if (!$this->hasResource($id)) {
482
            return $this->setResource($id, [
483
                'password' => $password,
484
            ]);
485
        }
486
487
        $resource = &$this->resources[$id];
488
        $resource['password'] = $password;
489
490
        return $this;
491
    }
492
493
    public function getPassword($id)
494
    {
495
        if (!$this->hasResource($id)) {
496
            return $this->getResource($id);
497
        }
498
499
        $resource = &$this->resources[$id];
500
501
        return $resource['password'];
502
    }
503
504
    /**
505
     * Normalize a list of servers into the following format:
506
     * array(array('host' => <host>, 'port' => <port>, 'weight' => <weight>)[, ...]).
507
     *
508
     * @param string|array $servers
509
     *
510
     * @throws \Zend\Stdlib\Exception\InvalidArgumentException
511
     */
512 61
    protected function normalizeServers(&$servers)
513
    {
514 61
        if (!is_array($servers) && !$servers instanceof \Traversable) {
515
            // Convert string into a list of servers
516
            $servers = explode(',', $servers);
517
        }
518 61
        $result = [];
519 61
        foreach ($servers as $server) {
520 61
            $this->normalizeServer($server);
521 61
            $result[$server['host'].':'.$server['port']] = $server;
522 61
        }
523 61
        $servers = array_values($result);
524 61
    }
525
526
    /**
527
     * Compare 2 normalized server arrays
528
     * (Compares only the host and the port).
529
     *
530
     * @param array $serverA
531
     * @param array $serverB
532
     *
533
     * @return int
534
     */
535
    protected function compareServers(array $serverA, array $serverB)
536
    {
537
        $keyA = $serverA['host'].':'.$serverA['port'];
538
        $keyB = $serverB['host'].':'.$serverB['port'];
539
        if ($keyA === $keyB) {
540
            return 0;
541
        }
542
543
        return $keyA > $keyB ? 1 : -1;
544
    }
545
}
546