Completed
Pull Request — master (#29)
by yuuki
05:25
created

CouchbaseConnection   C

Complexity

Total Complexity 65

Size/Duplication

Total Lines 521
Duplicated Lines 3.07 %

Coupling/Cohesion

Components 2
Dependencies 12

Test Coverage

Coverage 27.97%

Importance

Changes 0
Metric Value
dl 16
loc 521
ccs 33
cts 118
cp 0.2797
rs 5.7894
c 0
b 0
f 0
wmc 65
lcom 2
cbo 12

37 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A setBucketPassword() 0 6 1
A openBucket() 0 4 1
A manager() 0 4 1
A getOptions() 0 9 2
A registerOption() 0 8 3
A getDefaultPostProcessor() 0 4 1
A getDefaultQueryGrammar() 0 4 1
A getSchemaBuilder() 0 4 1
B getManagedConfigure() 0 13 6
A createConnection() 0 10 1
A getDriverName() 0 4 1
A getCouchbase() 0 8 2
A table() 0 4 1
A callableConsistency() 0 7 1
A consistency() 0 6 1
A bucket() 0 6 1
A executeQuery() 0 10 1
B select() 0 23 5
A insert() 0 4 1
B affectingStatement() 8 28 6
B positionalStatement() 8 26 6
A transaction() 0 4 1
A beginTransaction() 0 4 1
A commit() 0 4 1
A rollBack() 0 4 1
A reconnectIfMissingConnection() 0 6 2
A disconnect() 0 4 1
A enableN1ql() 0 9 2
A upsert() 0 4 1
A query() 0 6 1
A view() 0 6 2
A update() 0 4 1
A delete() 0 4 1
A metrics() 0 4 1
A firePreparedQuery() 0 6 2
A fireReturning() 0 6 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like CouchbaseConnection 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 CouchbaseConnection, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10
 * THE SOFTWARE.
11
 */
12
13
namespace Ytake\LaravelCouchbase\Database;
14
15
use Closure;
16
use CouchbaseBucket;
17
use Illuminate\Database\Connection;
18
use Ytake\LaravelCouchbase\Query\View;
19
use Ytake\LaravelCouchbase\Schema\Builder;
20
use Ytake\LaravelCouchbase\VersionTrait;
21
use Ytake\LaravelCouchbase\Query\Grammar;
22
use Ytake\LaravelCouchbase\Query\Processor;
23
use Ytake\LaravelCouchbase\Events\QueryPrepared;
24
use Ytake\LaravelCouchbase\Events\ResultReturning;
25
use Ytake\LaravelCouchbase\Query\Builder as QueryBuilder;
26
use Ytake\LaravelCouchbase\Exceptions\NotSupportedException;
27
28
/**
29
 * Class CouchbaseConnection.
30
 *
31
 * @author Yuuki Takezawa<[email protected]>
32
 */
33
class CouchbaseConnection extends Connection
34
{
35
    use VersionTrait;
36
37
    /** @var string */
38
    protected $bucket;
39
40
    /** @var \CouchbaseCluster */
41
    protected $connection;
42
43
    /** @var */
44
    protected $managerUser;
45
46
    /** @var */
47
    protected $managerPassword;
48
49
    /** @var array */
50
    protected $options = [];
51
52
    /** @var int */
53
    protected $fetchMode = 0;
54
55
    /** @var array */
56
    protected $enableN1qlServers = [];
57
58
    /** @var string */
59
    protected $bucketPassword = '';
60
61
    /** @var string[] */
62
    protected $metrics;
63
64
    /** @var int  default consistency */
65
    protected $consistency = \CouchbaseN1qlQuery::NOT_BOUNDED;
66
67
    /** @var string[]  function to handle the retrieval of various properties. */
68
    private $properties = [
69
        'operationTimeout',
70
        'viewTimeout',
71
        'durabilityInterval',
72
        'durabilityTimeout',
73
        'httpTimeout',
74
        'configTimeout',
75
        'configDelay',
76
        'configNodeTimeout',
77
        'htconfigIdleTimeout',
78
    ];
79
80
    /** @var array */
81
    protected $config = [];
82
83
    /**
84
     * @param array $config
85
     */
86 1
    public function __construct(array $config)
87
    {
88 1
        $this->config = $config;
89 1
        $this->getManagedConfigure($config);
90
91 1
        $this->useDefaultQueryGrammar();
92
93 1
        $this->useDefaultPostProcessor();
94 1
    }
95
96
    /**
97
     * @param $password
98
     *
99
     * @return $this
100
     */
101
    public function setBucketPassword($password)
102
    {
103
        $this->bucketPassword = $password;
104
105
        return $this;
106
    }
107
108
    /**
109
     * @param string $name
110
     *
111
     * @return \CouchbaseBucket
112
     *
113
     * @throws \CouchbaseException
114
     */
115 1
    public function openBucket($name)
116
    {
117 1
        return $this->getCouchbase()->openBucket($name, $this->bucketPassword);
118
    }
119
120
    /**
121
     * @return \CouchbaseClusterManager
122
     */
123
    public function manager()
124
    {
125
        return $this->getCouchbase()->manager($this->managerUser, $this->managerPassword);
126
    }
127
128
    /**
129
     * @param CouchbaseBucket $bucket
130
     *
131
     * @return string[]
132
     */
133
    public function getOptions(\CouchbaseBucket $bucket)
134
    {
135
        $options = [];
136
        foreach ($this->properties as $property) {
137
            $options[$property] = $bucket->$property;
138
        }
139
140
        return $options;
141
    }
142
143
    /**
144
     * @param CouchbaseBucket $bucket
145
     */
146
    protected function registerOption(\CouchbaseBucket $bucket)
147
    {
148
        if (count($this->options)) {
149
            foreach ($this->options as $option => $value) {
150
                $bucket->$option = $value;
151
            }
152
        }
153
    }
154
155
    /**
156
     * @return Processor
157
     */
158 1
    protected function getDefaultPostProcessor()
159
    {
160 1
        return new Processor();
161
    }
162
163
    /**
164
     * @return Grammar
165
     */
166 1
    protected function getDefaultQueryGrammar()
167
    {
168 1
        return new Grammar();
169
    }
170
171
    /**
172
     * @return Builder|\Illuminate\Database\Schema\Builder
173
     */
174
    public function getSchemaBuilder()
175
    {
176
        return new Builder($this);
177
    }
178
179
    /**
180
     *
181
     * @param array $config enable(array), options(array), administrator(array), bucket_password(string)
182
     */
183 1
    protected function getManagedConfigure(array $config)
184
    {
185 1
        $this->enableN1qlServers = (isset($config['enables'])) ? $config['enables'] : [];
186 1
        $this->options = (isset($config['options'])) ? $config['options'] : [];
187 1
        $manager = (isset($config['administrator'])) ? $config['administrator'] : null;
188 1
        $this->managerUser = '';
189 1
        $this->managerPassword = '';
190 1
        if (!is_null($manager)) {
191 1
            $this->managerUser = $config['administrator']['user'];
192 1
            $this->managerPassword = $config['administrator']['password'];
193
        }
194 1
        $this->bucketPassword = (isset($config['bucket_password'])) ? $config['bucket_password'] : '';
195 1
    }
196
197
    /**
198
     * @return \CouchbaseCluster
199
     */
200 1
    protected function createConnection()
201
    {
202
        $this->setReconnector(function ($connection) {
0 ignored issues
show
Unused Code introduced by
The parameter $connection is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
203
            $this->connection = (new CouchbaseConnector)->connect($this->config);
204
205
            return $this;
206 1
        });
207
208 1
        return (new CouchbaseConnector)->connect($this->config);
209
    }
210
211
    /**
212
     * {@inheritdoc}
213
     */
214
    public function getDriverName()
215
    {
216
        return 'couchbase';
217
    }
218
219
    /**
220
     * @return \CouchbaseCluster
221
     */
222 1
    public function getCouchbase()
223
    {
224 1
        if (is_null($this->connection)) {
225 1
            $this->connection = $this->createConnection();
226
        }
227
228 1
        return $this->connection;
229
    }
230
231
    /**
232
     * @param string $table
233
     *
234
     * @return QueryBuilder
235
     */
236
    public function table($table)
237
    {
238
        return $this->bucket($table)->query()->from($table);
0 ignored issues
show
Bug introduced by
The method from does only exist in Illuminate\Database\Query\Builder, but not in Ytake\LaravelCouchbase\Schema\Builder.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
239
    }
240
241
    /**
242
     * @param int      $consistency
243
     * @param callable $callback
244
     *
245
     * @return mixed
246
     */
247
    public function callableConsistency($consistency, callable $callback)
248
    {
249
        $clone = clone $this;
250
        $clone->consistency = $consistency;
251
252
        return call_user_func_array($callback, [$clone]);
253
    }
254
255
    /**
256
     * @param int $consistency
257
     *
258
     * @return $this
259
     */
260
    public function consistency($consistency)
261
    {
262
        $this->consistency = $consistency;
263
264
        return $this;
265
    }
266
267
    /**
268
     * @param string $bucket
269
     *
270
     * @return $this
271
     */
272
    public function bucket($bucket)
273
    {
274
        $this->bucket = $bucket;
275
276
        return $this;
277
    }
278
279
    /**
280
     * @param \CouchbaseN1qlQuery $query
281
     *
282
     * @return mixed
283
     */
284
    protected function executeQuery(\CouchbaseN1qlQuery $query)
285
    {
286
        $bucket = $this->openBucket($this->bucket);
287
        $this->registerOption($bucket);
288
        $this->firePreparedQuery($query);
289
        $result = $bucket->query($query);
290
        $this->fireReturning($result);
291
292
        return $result;
293
    }
294
295
    /**
296
     * {@inheritdoc}
297
     */
298
    public function select($query, $bindings = [], $useReadPdo = true)
299
    {
300
        return $this->run($query, $bindings, function ($me, $query, $bindings) {
301
            if ($me->pretending()) {
302
                return [];
303
            }
304
            $query = \CouchbaseN1qlQuery::fromString($query);
305
            if ($this->breakingVersion()) {
306
                $query->consistency($this->consistency);
307
                $query->positionalParams($bindings);
308
                $result = $this->executeQuery($query);
309
                $this->metrics = (isset($result->metrics)) ? $result->metrics : [];
310
311
                return (isset($result->rows)) ? $result->rows : [];
312
            }
313
            // @codeCoverageIgnoreStart
314
            $query->options['args'] = $bindings;
315
            $query->consistency($this->consistency);
316
317
            return $this->executeQuery($query);
318
            // @codeCoverageIgnoreEnd
319
        });
320
    }
321
322
    /**
323
     * @param string $query
324
     * @param array  $bindings
325
     *
326
     * @return int|mixed
327
     */
328
    public function insert($query, $bindings = [])
329
    {
330
        return $this->affectingStatement($query, $bindings);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->affectingS...ent($query, $bindings); (integer) is incompatible with the return type declared by the interface Illuminate\Database\ConnectionInterface::insert of type boolean.

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...
331
    }
332
333
    /**
334
     * {@inheritdoc}
335
     */
336
    public function affectingStatement($query, $bindings = [])
337
    {
338
        return $this->run($query, $bindings, function ($me, $query, $bindings) {
339
            if ($me->pretending()) {
340
                return 0;
341
            }
342
            $query = \CouchbaseN1qlQuery::fromString($query);
343
344 View Code Duplication
            if ($this->breakingVersion()) {
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...
345
                $query->consistency($this->consistency);
346
                $query->namedParams(['parameters' => $bindings]);
347
                $result = $this->executeQuery($query);
348
                $this->metrics = (isset($result->metrics)) ? $result->metrics : [];
349
350
                return (isset($result->rows[0])) ? $result->rows[0] : false;
351
            }
352
            // @codeCoverageIgnoreStart
353
            $query->consistency($this->consistency);
354
            $bucket = $this->openBucket($this->bucket);
355
            $this->registerOption($bucket);
356
            $this->firePreparedQuery($query);
357
            $result = $bucket->query($query, ['parameters' => $bindings]);
358
            $this->fireReturning($result);
359
360
            return (isset($result[0])) ? $result[0] : false;
361
            // @codeCoverageIgnoreEnd
362
        });
363
    }
364
365
    /**
366
     * @param       $query
367
     * @param array $bindings
368
     *
369
     * @return mixed
370
     */
371
    public function positionalStatement($query, array $bindings = [])
372
    {
373
        return $this->run($query, $bindings, function ($me, $query, $bindings) {
374
            if ($me->pretending()) {
375
                return 0;
376
            }
377
            $query = \CouchbaseN1qlQuery::fromString($query);
378
379 View Code Duplication
            if ($this->breakingVersion()) {
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...
380
                $query->consistency($this->consistency);
381
                $query->positionalParams($bindings);
382
                $result = $this->executeQuery($query);
383
                $this->metrics = (isset($result->metrics)) ? $result->metrics : [];
384
385
                return (isset($result->rows[0])) ? $result->rows[0] : false;
386
            }
387
388
            // @codeCoverageIgnoreStart
389
            $query->consistency($this->consistency);
390
            $query->options['args'] = $bindings;
391
            $result = $this->executeQuery($query);
392
393
            return (isset($result[0])) ? $result[0] : false;
394
            // @codeCoverageIgnoreEnd
395
        });
396
    }
397
398
    /**
399
     * {@inheritdoc}
400
     */
401
    public function transaction(Closure $callback)
402
    {
403
        throw new NotSupportedException(__METHOD__);
404
    }
405
406
    /**
407
     * {@inheritdoc}
408
     */
409
    public function beginTransaction()
410
    {
411
        throw new NotSupportedException(__METHOD__);
412
    }
413
414
    /**
415
     * {@inheritdoc}
416
     */
417
    public function commit()
418
    {
419
        throw new NotSupportedException(__METHOD__);
420
    }
421
422
    /**
423
     * {@inheritdoc}
424
     */
425
    public function rollBack()
426
    {
427
        throw new NotSupportedException(__METHOD__);
428
    }
429
430
    /**
431
     * {@inheritdoc}
432
     */
433
    protected function reconnectIfMissingConnection()
434
    {
435
        if (is_null($this->connection)) {
436
            $this->reconnect();
437
        }
438
    }
439
440
    /**
441
     * {@inheritdoc}
442
     */
443
    public function disconnect()
444
    {
445
        $this->connection = null;
446
    }
447
448
    /**
449
     * @param CouchbaseBucket $bucket
450
     *
451
     * @return CouchbaseBucket
452
     */
453
    protected function enableN1ql(CouchbaseBucket $bucket)
454
    {
455
        if (!count($this->enableN1qlServers)) {
456
            return $bucket;
457
        }
458
        $bucket->enableN1ql($this->enableN1qlServers);
459
460
        return $bucket;
461
    }
462
463
    /**
464
     * N1QL upsert query.
465
     *
466
     * @param string $query
467
     * @param array  $bindings
468
     *
469
     * @return int
470
     */
471
    public function upsert($query, $bindings = [])
472
    {
473
        return $this->affectingStatement($query, $bindings);
474
    }
475
476
    /**
477
     * Get a new query builder instance.
478
     *
479
     * @return QueryBuilder|\Illuminate\Database\Query\Builder|Builder
480
     */
481
    public function query()
482
    {
483
        return new QueryBuilder(
484
            $this, $this->getQueryGrammar(), $this->getPostProcessor()
485
        );
486
    }
487
488
    /**
489
     * @param string|null $bucket
490
     *
491
     * @return View
492
     */
493 1
    public function view($bucket = null)
494
    {
495 1
        $bucket = is_null($bucket) ? $this->bucket : $bucket;
496
497 1
        return new View($this->openBucket($bucket), $this->events);
498
    }
499
500
    /**
501
     * Run an update statement against the database.
502
     *
503
     * @param string $query
504
     * @param array  $bindings
505
     *
506
     * @return int|\stdClass
507
     */
508
    public function update($query, $bindings = [])
509
    {
510
        return $this->positionalStatement($query, $bindings);
511
    }
512
513
    /**
514
     * Run a delete statement against the database.
515
     *
516
     * @param string $query
517
     * @param array  $bindings
518
     *
519
     * @return int|\stdClass
520
     */
521
    public function delete($query, $bindings = [])
522
    {
523
        return $this->positionalStatement($query, $bindings);
524
    }
525
526
    /**
527
     * @return \string[]
528
     */
529
    public function metrics()
530
    {
531
        return $this->metrics;
532
    }
533
534
    /**
535
     * @param \CouchbaseN1qlQuery $queryObject
536
     */
537
    protected function firePreparedQuery(\CouchbaseN1qlQuery $queryObject)
538
    {
539
        if (isset($this->events)) {
540
            $this->events->fire(new QueryPrepared($queryObject));
541
        }
542
    }
543
544
    /**
545
     * @param mixed $returning
546
     */
547
    protected function fireReturning($returning)
548
    {
549
        if (isset($this->events)) {
550
            $this->events->fire(new ResultReturning($returning));
551
        }
552
    }
553
}
554