Completed
Push — develop ( c0c47b...cc4ea9 )
by Siad
04:17
created

CouchbaseResourceManager::addServers()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 25
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 25
ccs 0
cts 18
cp 0
rs 8.5806
cc 4
eloc 15
nc 4
nop 2
crap 20
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 62
    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 62
        $host = null;
71 62
        $port = 8091;
72
        // convert a single server into an array
73 62
        if ($server instanceof \Traversable) {
74
            $server = ArrayUtils::iteratorToArray($server);
75
        }
76 62
        if (is_array($server)) {
77
            // array(<host>[, <port>])
78 62
            if (isset($server[0])) {
79 62
                $host = (string) $server[0];
80 62
                $port = isset($server[1]) ? (int) $server[1] : $port;
81 62
            }
82
            // array('host' => <host>[, 'port' => <port>])
83 62
            if (!isset($server[0]) && isset($server['host'])) {
84
                $host = (string) $server['host'];
85
                $port = isset($server['port']) ? (int) $server['port'] : $port;
86
            }
87 62
        } else {
88
            // parse server from URI host{:?port}
89 1
            $server = trim($server);
90 1
            if (strpos($server, '://') === false) {
91 1
                $server = 'couchbase://'.$server;
92 1
            }
93 1
            $server = parse_url($server);
94 1
            if (!$server) {
95 1
                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 62
        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 62
            'host' => $host,
109 62
            'port' => $port,
110 62
        ];
111 62
    }
112
113
    /**
114
     * Check if a resource exists.
115
     *
116
     * @param string $id
117
     *
118
     * @return bool
119
     */
120 62
    public function hasResource($id)
121
    {
122 62
        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 62
    public function getResource($id)
135
    {
136 62
        if (!$this->hasResource($id)) {
137
            throw new Exception\RuntimeException("No resource with id '{$id}'");
138
        }
139 62
        $resource = $this->resources[$id];
140 62
        if ($resource instanceof \CouchbaseBucket) {
141 62
            return $resource;
142
        }
143 62
        $memc = new CouchbaseClusterResource('couchbase://'.$resource['server'][0]['host'].':'.$resource['server'][0]['port'], (string) $resource['username'], (string) $resource['password'], \COUCHBASE_SERTYPE_PHP);
144 62
        $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 62
        $this->resources[$id] = $bucket;
154
155 62
        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 62
    public function setResource($id, $resource)
170
    {
171 62
        $id = (string) $id;
172 62
        if (!($resource instanceof CouchbaseBucketResource)) {
173 62
            if ($resource instanceof \Traversable) {
174
                $resource = ArrayUtils::iteratorToArray($resource);
175 62
            } 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 62
            $resource = array_merge([
181 62
                'lib_options' => [],
182 62
                'server' => '',
183 62
                'username' => '',
184 62
                'password' => '',
185 62
                'bucket' => '',
186 62
            ], $resource);
187
            // normalize and validate params
188 62
            $this->normalizeServers($resource['server']);
189 62
            $this->normalizeLibOptions($resource['lib_options']);
190 62
        }
191 62
        $this->resources[$id] = $resource;
192
193 62
        return $this;
194
    }
195
196
    /**
197
     * Normalize libmemcached options.
198
     *
199
     * @param array|\Traversable $libOptions
200
     *
201
     * @throws Exception\InvalidArgumentException
202
     */
203 62
    protected function normalizeLibOptions(&$libOptions)
204
    {
205 62
        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 62
        $result = [];
212 62
        foreach ($libOptions as $key => $value) {
213
            $this->normalizeLibOptionKey($key);
214
            $result[$key] = $value;
215 62
        }
216
217 62
        $libOptions = $result;
218 62
    }
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_'.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 1
    public function setLibOptions($id, array $libOptions)
250
    {
251 1
        if (!$this->hasResource($id)) {
252 1
            return $this->setResource($id, [
253 1
                'lib_options' => $libOptions,
254 1
            ]);
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 62
    public function setServer($id, $server)
327
    {
328 62
        if (!$this->hasResource($id)) {
329 62
            return $this->setResource($id, [
330 62
                'server' => $server,
331 62
            ]);
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 62
    public function setBucket($id, $bucket)
441
    {
442 62
        if (!$this->hasResource($id)) {
443
            return $this->setResource($id, [
444
                'bucket' => $bucket,
445
            ]);
446
        }
447
448 62
        $resource = &$this->resources[$id];
449 62
        $resource['bucket'] = $bucket;
450
451 62
        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 62
    protected function normalizeServers(&$servers)
513
    {
514 62
        if (!is_array($servers) && !$servers instanceof \Traversable) {
515
            // Convert string into a list of servers
516 1
            $servers = explode(',', $servers);
517 1
        }
518 62
        $result = [];
519 62
        foreach ($servers as $server) {
520 62
            $this->normalizeServer($server);
521 62
            $result[$server['host'].':'.$server['port']] = $server;
522 62
        }
523 62
        $servers = array_values($result);
524 62
    }
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