Completed
Pull Request — master (#44)
by yuuki
01:19
created

CouchbaseConnection::getSchemaBuilder()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
ccs 0
cts 0
cp 0
crap 2
1
<?php
2
declare(strict_types=1);
3
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
11
 * THE SOFTWARE.
12
 */
13
14
namespace Ytake\LaravelCouchbase\Database;
15
16
use Closure;
17
use Couchbase\Bucket;
18
use Couchbase\Cluster;
19
use Couchbase\ClusterManager;
20
use Couchbase\N1qlQuery;
21
use Illuminate\Database\Connection;
22
use Ytake\LaravelCouchbase\Events\QueryPrepared;
23
use Ytake\LaravelCouchbase\Events\ResultReturning;
24
use Ytake\LaravelCouchbase\Exceptions\NotSupportedException;
25
use Ytake\LaravelCouchbase\Query\Builder as QueryBuilder;
26
use Ytake\LaravelCouchbase\Query\Grammar;
27
use Ytake\LaravelCouchbase\Query\Processor;
28
use Ytake\LaravelCouchbase\Query\View;
29
use Ytake\LaravelCouchbase\Schema\Builder;
30
31
/**
32
 * Class CouchbaseConnection.
33
 *
34
 * @author Yuuki Takezawa<[email protected]>
35
 */
36
class CouchbaseConnection extends Connection
37
{
38
    /** @var string */
39
    protected $bucket;
40
41
    /** @var Cluster */
42
    protected $connection;
43
44
    /** @var */
45
    protected $managerUser;
46
47
    /** @var */
48
    protected $managerPassword;
49
50
    /** @var array */
51
    protected $options = [];
52
53
    /** @var int */
54
    protected $fetchMode = 0;
55
56
    /** @var array */
57
    protected $enableN1qlServers = [];
58
59
    /** @var string */
60
    protected $bucketPassword = '';
61
62
    /** @var string[] */
63
    protected $metrics;
64
65
    /** @var int  default consistency */
66
    protected $consistency = N1qlQuery::NOT_BOUNDED;
67
68
    /** @var string[]  function to handle the retrieval of various properties. */
69
    private $properties = [
70
        'operationTimeout',
71
        'viewTimeout',
72
        'durabilityInterval',
73
        'durabilityTimeout',
74
        'httpTimeout',
75
        'configTimeout',
76
        'configDelay',
77
        'configNodeTimeout',
78
        'htconfigIdleTimeout',
79
    ];
80
81
    /** @var array */
82
    protected $config = [];
83
84
    /** @var string */
85
    private $name;
86
87 44
    /** @var bool */
88
    private $crossBucket = true;
89 44
90 44
    /**
91 44
     * @param array  $config
92
     * @param string $name
93 44
     */
94
    public function __construct(array $config, $name)
95 44
    {
96 44
        $this->config = $config;
97
        $this->name = $name;
98
        $this->getManagedConfigure($config);
99
100
        $this->useDefaultQueryGrammar();
101
102
        $this->useDefaultPostProcessor();
103
    }
104
105
    /**
106
     * @param string $password
107
     *
108
     * @return CouchbaseConnection
109
     */
110
    public function setBucketPassword(string $password): CouchbaseConnection
111
    {
112
        $this->bucketPassword = $password;
113
114
        return $this;
115
    }
116
117 19
    /**
118
     * @param string $name
119 19
     *
120
     * @return Bucket
121
     */
122
    public function openBucket(string $name): Bucket
123
    {
124
        return $this->getCouchbase()->openBucket($name, $this->bucketPassword);
125 2
    }
126
127 2
    /**
128
     * @return ClusterManager
129
     */
130
    public function manager(): ClusterManager
131
    {
132
        return $this->getCouchbase()->manager($this->managerUser, $this->managerPassword);
133
    }
134
135 1
    /**
136
     * @param Bucket $bucket
137 1
     *
138 1
     * @return string[]
139 1
     */
140
    public function getOptions(Bucket $bucket): array
141
    {
142 1
        $options = [];
143
        foreach ($this->properties as $property) {
144
            $options[$property] = $bucket->$property;
145
        }
146
147
        return $options;
148 9
    }
149
150 9
    /**
151
     * @param Bucket $bucket
152
     */
153
    protected function registerOption(Bucket $bucket)
154
    {
155 9
        if (count($this->options)) {
156
            foreach ($this->options as $option => $value) {
157
                $bucket->$option = $value;
158
            }
159
        }
160 44
    }
161
162 44
    /**
163
     * @return Processor
164
     */
165
    protected function getDefaultPostProcessor()
166
    {
167
        return new Processor();
168 44
    }
169
170 44
    /**
171
     * @return Grammar
172
     */
173
    protected function getDefaultQueryGrammar()
174
    {
175
        return new Grammar();
176 6
    }
177
178 6
    /**
179
     * @return Builder|\Illuminate\Database\Schema\Builder
180
     */
181
    public function getSchemaBuilder()
182
    {
183
        return new Builder($this);
184
    }
185 44
186
    /**
187 44
     * @param array $config enable(array), options(array), administrator(array), bucket_password(string)
188 44
     */
189 44
    protected function getManagedConfigure(array $config)
190 44
    {
191 44
        $this->enableN1qlServers = (isset($config['enables'])) ? $config['enables'] : [];
192 44
        $this->options = (isset($config['options'])) ? $config['options'] : [];
193 44
        $manager = (isset($config['administrator'])) ? $config['administrator'] : null;
194 44
        $this->managerUser = '';
195
        $this->managerPassword = '';
196 44
        if (!is_null($manager)) {
197 44
            $this->managerUser = $config['administrator']['user'];
198
            $this->managerPassword = $config['administrator']['password'];
199
        }
200
        $this->bucketPassword = (isset($config['bucket_password'])) ? $config['bucket_password'] : '';
201
    }
202 10
203
    /**
204 10
     * {@inheritdoc}
205
     */
206
    public function getName()
207
    {
208
        return $this->name;
209
    }
210 35
211
    /**
212
     * @return \Couchbase\Cluster
213
     */
214
    protected function createConnection(): Cluster
215
    {
216 35
        $this->setReconnector(function () {
217
            $this->connection = (new CouchbaseConnector)->connect($this->config);
218 35
219
            return $this;
220
        });
221
222
        return (new CouchbaseConnector)->connect($this->config);
223
    }
224
225
    /**
226
     * {@inheritdoc}
227
     */
228
    public function getDriverName()
229
    {
230
        return 'couchbase';
231
    }
232 34
233
    /**
234 34
     * @return Cluster
235 28
     */
236
    public function getCouchbase(): Cluster
237
    {
238 34
        if (is_null($this->connection)) {
239
            $this->connection = $this->createConnection();
240
        }
241
242
        return $this->connection;
243
    }
244
245
    /**
246 9
     * @param string $table
247
     *
248 9
     * @return QueryBuilder
249
     */
250
    public function table($table)
251
    {
252
        return $this->bucket($table)->query()->from($table);
253
    }
254
255
    /**
256
     * @param int      $consistency
257 1
     * @param callable $callback
258
     *
259 1
     * @return mixed
260 1
     */
261
    public function callableConsistency(int $consistency, callable $callback)
262 1
    {
263
        $clone = clone $this;
264
        $clone->consistency = $consistency;
265
266
        return call_user_func_array($callback, [$clone]);
267
    }
268
269
    /**
270
     * @param int $consistency
271
     *
272
     * @return CouchbaseConnection
273
     */
274
    public function consistency(int $consistency): CouchbaseConnection
275
    {
276
        $this->consistency = $consistency;
277
278
        return $this;
279
    }
280
281
    /**
282 10
     * @param bool $cross
283
     */
284 10
    public function crossBucket(bool $cross)
285
    {
286 10
        $this->crossBucket = $cross;
287
    }
288
289
    /**
290
     * @param string $bucket
291
     *
292
     * @return $this
293
     */
294 9
    public function bucket(string $bucket)
295
    {
296 9
        $this->bucket = $bucket;
297 9
298 9
        return $this;
299 9
    }
300 9
301
    /**
302 9
     * @param N1qlQuery $query
303
     *
304
     * @return mixed
305
     */
306
    protected function executeQuery(N1qlQuery $query)
307
    {
308 5
        $bucket = $this->openBucket($this->bucket);
309
        $this->registerOption($bucket);
310
        $this->firePreparedQuery($query);
311 5
        $result = $bucket->query($query);
312
        $this->fireReturning($result);
313
314 5
        return $result;
315 5
    }
316 5
317 5
    /**
318 5
     * @param string $query
319
     * @param array  $bindings
320 5
     *
321 5
     * @return \stdClass
322
     */
323
    protected function execute(string $query, array $bindings = []): \stdClass
324
    {
325
        $query = N1qlQuery::fromString($query);
326
        $query->consistency($this->consistency);
327
        $query->crossBucket($this->crossBucket);
328
        $query->positionalParams($bindings);
329
        $result = $this->executeQuery($query);
330 5
        $this->metrics = $result->metrics ?? [];
331
332 5
        return $result;
333
    }
334
335
    /**
336
     * {@inheritdoc}
337
     */
338 6
    public function select($query, $bindings = [], $useReadPdo = true)
339
    {
340
        return $this->run($query, $bindings, function ($query, $bindings) {
341 6
            if ($this->pretending()) {
342
                return [];
343
            }
344 6
345 6
            $result = $this->execute($query, $bindings);
346 6
            $returning = [];
347 6
            if (isset($result->rows)) {
348 6
                foreach ($result->rows as $row) {
349
                    if (!isset($row->{$this->bucket})) {
350 6
                        return [$row];
351 6
                    }
352
                    $returning[] = $row;
353
                }
354
            }
355
356
            return $returning;
357
        });
358
    }
359
360
    /**
361
     * {@inheritdoc}
362 3
     */
363 3
    public function cursor($query, $bindings = [], $useReadPdo = true)
364
    {
365
        return $this->run($query, $bindings, function ($query, $bindings) {
366 3
            if ($this->pretending()) {
367 3
                return [];
368 3
            }
369 3
370 3
            $result = $this->execute($query, $bindings);
371
            if (isset($result->rows)) {
372 3
                foreach ($result->rows as $row) {
373 3
                    yield $row->{$this->bucket};
374
                }
375
            }
376
        });
377
    }
378
379 1
    /**
380
     * @param string $query
381 1
     * @param array  $bindings
382
     *
383
     * @return int|mixed
384
     */
385
    public function insert($query, $bindings = [])
386
    {
387 1
        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...
388
    }
389 1
390
    /**
391
     * {@inheritdoc}
392
     */
393
    public function affectingStatement($query, $bindings = [])
394
    {
395 1
        return $this->run($query, $bindings, function ($query, $bindings) {
396
            if ($this->pretending()) {
397 1
                return 0;
398
            }
399
            $query = N1qlQuery::fromString($query);
400
            $query->consistency($this->consistency);
401
            $query->crossBucket($this->crossBucket);
402
            $query->namedParams(['parameters' => $bindings]);
403 1
            $result = $this->executeQuery($query);
404
            $this->metrics = $result->metrics ?? [];
405 1
            if (!count($result->rows)) {
406
                return false;
407
            }
408
409
            return $result->rows[0]->{$this->bucket} ?? $result->rows[0];
410
        });
411 9
    }
412
413 9
    /**
414 6
     * @param string $query
415
     * @param array  $bindings
416 9
     *
417
     * @return mixed
418
     */
419
    public function positionalStatement(string $query, array $bindings = [])
420
    {
421 7
        return $this->run($query, $bindings, function ($query, $bindings) {
422
            if ($this->pretending()) {
423 7
                return 0;
424 7
            }
425
            $query = N1qlQuery::fromString($query);
426
            $query->consistency($this->consistency);
427
            $query->crossBucket($this->crossBucket);
428
            $query->positionalParams($bindings);
429
            $result = $this->executeQuery($query);
430
            $this->metrics = $result->metrics ?? [];
431
            if (!count($result->rows)) {
432
                return false;
433
            }
434
435
            return $result->rows[0]->{$this->bucket} ?? $result->rows[0];
436
        });
437
    }
438
439
    /**
440
     * {@inheritdoc}
441
     */
442
    public function transaction(Closure $callback, $attempts = 1)
443
    {
444
        throw new NotSupportedException(__METHOD__);
445
    }
446
447
    /**
448
     * {@inheritdoc}
449 1
     */
450
    public function beginTransaction()
451 1
    {
452
        throw new NotSupportedException(__METHOD__);
453
    }
454
455
    /**
456
     * {@inheritdoc}
457
     */
458
    public function commit()
459 9
    {
460
        throw new NotSupportedException(__METHOD__);
461 9
    }
462 9
463
    /**
464
     * {@inheritdoc}
465
     */
466
    public function rollBack()
467
    {
468
        throw new NotSupportedException(__METHOD__);
469
    }
470
471 1
    /**
472
     * {@inheritdoc}
473 1
     */
474
    protected function reconnectIfMissingConnection()
475 1
    {
476
        if (is_null($this->connection)) {
477
            $this->reconnect();
478
        }
479
    }
480
481
    /**
482
     * {@inheritdoc}
483
     */
484
    public function disconnect()
485
    {
486 1
        $this->connection = null;
487
    }
488 1
489
    /**
490
     * N1QL upsert query.
491
     *
492
     * @param string $query
493
     * @param array  $bindings
494
     *
495
     * @return int
496
     */
497
    public function upsert(string $query, array $bindings = [])
498
    {
499 2
        return $this->affectingStatement($query, $bindings);
500
    }
501 2
502
    /**
503
     * Get a new query builder instance.
504
     *
505
     * @return QueryBuilder
506
     */
507 1
    public function query()
508
    {
509 1
        return new QueryBuilder(
510
            $this, $this->getQueryGrammar(), $this->getPostProcessor()
511
        );
512
    }
513
514
    /**
515 9
     * @param string|null $bucket
516
     *
517 9
     * @return View
518 9
     */
519
    public function view(string $bucket = null): View
520 9
    {
521
        $bucket = is_null($bucket) ? $this->bucket : $bucket;
522
523
        return new View($this->openBucket($bucket), $this->events);
524
    }
525 9
526
    /**
527 9
     * Run an update statement against the database.
528 9
     *
529
     * @param string $query
530 9
     * @param array  $bindings
531
     *
532
     * @return int|\stdClass
533
     */
534
    public function update($query, $bindings = [])
535
    {
536
        return $this->positionalStatement($query, $bindings);
537 7
    }
538
539 7
    /**
540 7
     * Run a delete statement against the database.
541 7
     *
542 7
     * @param string $query
543 7
     * @param array  $bindings
544
     *
545
     * @return int|\stdClass
546
     */
547
    public function delete($query, $bindings = [])
548
    {
549
        return $this->positionalStatement($query, $bindings);
550
    }
551
552
    /**
553
     * @return \string[]
554
     */
555
    public function metrics(): array
556
    {
557
        return $this->metrics;
558
    }
559
560
    /**
561
     * @param N1qlQuery $queryObject
562
     */
563
    protected function firePreparedQuery(N1qlQuery $queryObject)
564
    {
565
        if (isset($this->events)) {
566
            $this->events->dispatch(new QueryPrepared($queryObject));
567
        }
568
    }
569
570
    /**
571
     * @param mixed $returning
572
     */
573
    protected function fireReturning($returning)
574
    {
575
        if (isset($this->events)) {
576
            $this->events->dispatch(new ResultReturning($returning));
577
        }
578
    }
579
580
    /**
581
     * @param null|\PDO $pdo
582
     *
583
     * @return $this
584
     */
585
    public function setPdo($pdo)
586
    {
587
        $this->connection = $this->createConnection();
588
        $this->getManagedConfigure($this->config);
589
        $this->useDefaultQueryGrammar();
590
        $this->useDefaultPostProcessor();
591
592
        return $this;
593
    }
594
}
595