Passed
Pull Request — master (#6)
by
unknown
04:57
created

Blueprint::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 10
c 0
b 0
f 0
rs 10
cc 2
nc 2
nop 4
1
<?php
2
3
namespace LaravelFreelancerNL\Aranguent\Schema;
4
5
use Closure;
6
use Illuminate\Support\Fluent;
7
use Illuminate\Database\Connection;
8
use Illuminate\Support\Traits\Macroable;
9
use Illuminate\Database\Schema\Grammars\Grammar as IlluminateGrammar;
10
11
/**
12
 * Class Blueprint.
13
 *
14
 * The Schema blueprint works differently from the standard Illuminate version:
15
 * 1) ArangoDB is schemaless: we don't need to (and can't) create columns
16
 * 2) ArangoDB doesn't allow DB schema actions within AQL nor within a transaction.
17
 *
18
 * This means that:
19
 * 1) We catch column related methods silently for backwards compatibility and ease of migrating from one DB type to another
20
 * 2) We don't need to compile AQL for transactions within the accompanying schema grammar. (for now)
21
 * 3) We can just execute each command on order. We will gather them first for possible future optimisations.
22
 */
23
class Blueprint
24
{
25
    use Macroable;
0 ignored issues
show
Bug introduced by
The trait Illuminate\Support\Traits\Macroable requires the property $name which is not provided by LaravelFreelancerNL\Aranguent\Schema\Blueprint.
Loading history...
26
27
    /**
28
     * The connection that is used by the blueprint.
29
     *
30
     * @var \Illuminate\Database\Connection
31
     */
32
    protected $connection;
33
34
    /**
35
     * The grammar that is used by the blueprint.
36
     *
37
     * @var \LaravelFreelancerNL\Aranguent\Schema\Grammars\Grammar
0 ignored issues
show
Bug introduced by
The type LaravelFreelancerNL\Aran...Schema\Grammars\Grammar was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
38
     */
39
    protected $grammar;
40
41
    /**
42
     * The collection the blueprint describes.
43
     *
44
     * @var string
45
     */
46
    protected $collection;
47
48
    /**
49
     * The handler for collection manipulation.
50
     */
51
    protected $collectionHandler;
52
53
    /**
54
     * The prefix of the collection.
55
     *
56
     * @var string
57
     */
58
    protected $prefix;
59
60
    /**
61
     * The commands that should be run for the collection.
62
     *
63
     * @var Fluent[]
64
     */
65
    protected $commands = [];
66
67
    /**
68
     * Catching attributes to be able to add fluent indexes.
69
     *
70
     * @var array
71
     */
72
    protected $attributes = [];
73
74
    /**
75
     * Whether to make the collection temporary.
76
     *
77
     * @var bool
78
     */
79
    public $temporary = false;
80
81
    /**
82
     * Detect if _key (and thus proxy _id) should autoincrement.
83
     *
84
     * @var bool
85
     */
86
    protected $autoIncrement = false;
87
88
    /**
89
     * Create a new schema blueprint.
90
     *
91
     * Blueprint constructor.
92
     * @param string $collection
93
     * @param \ArangoDBClient\CollectionHandler $collectionHandler
94
     * @param Closure|null $callback
95
     * @param string $prefix
96
     */
97
    public function __construct($collection, $collectionHandler, Closure $callback = null, $prefix = '')
98
    {
99
        $this->collection = $collection;
100
101
        $this->collectionHandler = $collectionHandler;
102
103
        $this->prefix = $prefix;
104
105
        if (! is_null($callback)) {
106
            $callback($this);
107
        }
108
    }
109
110
    /**
111
     * Execute the blueprint against the database.
112
     *
113
     * @param  \Illuminate\Database\Connection  $connection
114
     * @param  \Illuminate\Database\Schema\Grammars\Grammar  $grammar
115
     * @return void
116
     */
117
    public function build(Connection $connection, IlluminateGrammar $grammar)
118
    {
119
        $this->connection = $connection;
120
        $this->grammar = $connection->getSchemaGrammar();
0 ignored issues
show
Documentation Bug introduced by
It seems like $connection->getSchemaGrammar() of type Illuminate\Database\Schema\Grammars\Grammar is incompatible with the declared type LaravelFreelancerNL\Aran...Schema\Grammars\Grammar of property $grammar.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
121
122
        foreach ($this->commands as $command) {
123
            if ($command->handler == 'aql') {
124
                $command = $this->compileAqlCommand($command);
125
            }
126
127
            $this->executeCommand($command);
128
        }
129
    }
130
131
    /**
132
     * Generate the compilation method name and call it if method exists in the Grammar object.
133
     *
134
     * @param $command
135
     * @return mixed
136
     */
137
    public function compileAqlCommand($command)
138
    {
139
        $compileMethod = 'compile'.ucfirst($command->name);
140
        if (method_exists($this->grammar, $compileMethod)) {
141
            return $this->grammar->$compileMethod($this->collection, $command);
142
        }
143
    }
144
145
    /**
146
     * Generate the execution method name and call it if the method exists.
147
     *
148
     * @param $command
149
     */
150
    public function executeCommand($command)
151
    {
152
        $executeNamedMethod = 'execute'.ucfirst($command->name).'Command';
153
        $executeHandlerMethod = 'execute'.ucfirst($command->handler).'Command';
154
        if (method_exists($this, $executeNamedMethod)) {
155
            $this->$executeNamedMethod($command);
156
        } elseif (method_exists($this, $executeHandlerMethod)) {
157
            $this->$executeHandlerMethod($command);
158
        }
159
    }
160
161
    /**
162
     * Execute an AQL statement.
163
     *
164
     * @param $command
165
     */
166
    public function executeAqlCommand($command)
167
    {
168
        $this->connection->statement($command->aqb->query, $command->aqb->binds);
169
    }
170
171
    public function executeCollectionCommand($command)
172
    {
173
        if ($this->connection->pretending()) {
174
            $this->connection->logQuery('/* '.$command->explanation." */\n", []);
175
176
            return;
177
        }
178
179
        if (method_exists($this->collectionHandler, $command->method)) {
180
            $this->collectionHandler->{$command->method}($command->parameters);
181
        }
182
    }
183
184
    /**
185
     * Drop the index by first getting all the indexes on the collection; then selecting the matching one
186
     * by type and attributes.
187
     * @param $command
188
     * @throws \ArangoDBClient\Exception
189
     */
190
    public function executeDropIndexCommand($command)
191
    {
192
        if ($this->connection->pretending()) {
193
            $this->connection->logQuery('/* '.$command->explanation." */\n", []);
194
195
            return;
196
        }
197
        $attributes = $command->attributes;
198
        if (is_string($attributes)) {
199
            $attributes = [$attributes];
200
        }
201
        $indexId = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $indexId is dead and can be removed.
Loading history...
202
        $indexData = $this->collectionHandler->getIndexes($this->collection);
203
        foreach ($indexData['indexes'] as $key => $index) {
204
            if (
205
                $index['type'] === $command->type
206
                && empty(array_diff($index['fields'], $attributes))
207
                && empty(array_diff($attributes, $index['fields']))
208
            ) {
209
                $this->collectionHandler->dropIndex($index['id']);
210
            }
211
        }
212
    }
213
214
    /**
215
     * @param $command
216
     * @throws \ArangoDBClient\Exception
217
     */
218
    public function executeIndexCommand($command)
219
    {
220
        if ($this->connection->pretending()) {
221
            $this->connection->logQuery('/* '.$command->explanation." */\n", []);
222
223
            return;
224
        }
225
226
        $this->collectionHandler->index($this->collection, $command->type, $command->attributes, $command->unique, $command->indexOptions);
0 ignored issues
show
Deprecated Code introduced by
The function ArangoDBClient\CollectionHandler::index() has been deprecated: use CollectionHandler::createIndex instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

226
        /** @scrutinizer ignore-deprecated */ $this->collectionHandler->index($this->collection, $command->type, $command->attributes, $command->unique, $command->indexOptions);

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

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

Loading history...
227
    }
228
229
    /**
230
     * Solely provides feedback to the developer in pretend mode.
231
     *
232
     * @param $command
233
     * @return null
234
     */
235
    public function executeIgnoreCommand($command)
236
    {
237
        if ($this->connection->pretending()) {
238
            $this->connection->logQuery('/* '.$command->explanation." */\n", []);
239
240
            return;
241
        }
242
    }
243
244
    /**
245
     * Determine if the blueprint has a create command.
246
     *
247
     * @return bool
248
     */
249
    protected function creating()
250
    {
251
        return collect($this->commands)->contains(function ($command) {
252
            return $command->name === 'create';
253
        });
254
    }
255
256
    /**
257
     * Indicate that the collection needs to be created.
258
     *
259
     * @param array $options
260
     * @return Fluent
261
     */
262
    public function create($options = [])
263
    {
264
        $parameters['options'] = $options;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$parameters was never initialized. Although not strictly required by PHP, it is generally a good practice to add $parameters = array(); before regardless.
Loading history...
265
        $parameters['explanation'] = "Create '{$this->collection}' collection.";
266
        $parameters['handler'] = 'collection';
267
268
        return $this->addCommand('create', $parameters);
269
    }
270
271
    public function executeCreateCommand($command)
272
    {
273
        if ($this->connection->pretending()) {
274
            $this->connection->logQuery('/* '.$command->explanation." */\n", []);
275
276
            return;
277
        }
278
        $options = $command->options;
279
        if ($this->temporary === true) {
280
            $options['isVolatile'] = true;
281
        }
282
        if ($this->autoIncrement === true) {
283
            $options['keyOptions']['autoincrement'] = true;
284
        }
285
286
        $collections = $this->collectionHandler->getAllCollections(['excludeSystem' => true]);
287
        if (! isset($collections[$this->collection])) {
288
            $this->collectionHandler->create($this->collection, $options);
289
        }
290
    }
291
292
    /**
293
     * Indicate that the collection should be dropped.
294
     *
295
     * @return Fluent
296
     */
297
    public function drop()
298
    {
299
        $parameters['explanation'] = "Drop the '{$this->collection}' collection.";
0 ignored issues
show
Comprehensibility Best Practice introduced by
$parameters was never initialized. Although not strictly required by PHP, it is generally a good practice to add $parameters = array(); before regardless.
Loading history...
300
        $parameters['handler'] = 'collection';
301
302
        return $this->addCommand('drop', $parameters);
303
    }
304
305
    /**
306
     * Indicate that the collection should be dropped if it exists.
307
     *
308
     * @return Fluent
309
     */
310
    public function dropIfExists()
311
    {
312
        $parameters['explanation'] = "Drop the '{$this->collection}' collection.";
0 ignored issues
show
Comprehensibility Best Practice introduced by
$parameters was never initialized. Although not strictly required by PHP, it is generally a good practice to add $parameters = array(); before regardless.
Loading history...
313
        $parameters['handler'] = 'collection';
314
315
        return $this->addCommand('dropIfExists');
316
    }
317
318
    /**
319
     * Indicate that the given attribute(s) should be dropped.
320
     *
321
     * @param  array|mixed  $attributes
322
     * @return Fluent
323
     */
324
    public function dropColumn($attributes)
325
    {
326
        $attributes = is_array($attributes) ? $attributes : func_get_args();
327
328
        $parameters['handler'] = 'aql';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$parameters was never initialized. Although not strictly required by PHP, it is generally a good practice to add $parameters = array(); before regardless.
Loading history...
329
        $parameters['attributes'] = $attributes;
330
        $parameters['explanation'] = 'Drop the following attribute(s): '.implode(',', $attributes).'.';
331
332
        return $this->addCommand('dropAttribute', compact('parameters'));
333
    }
334
335
    /**
336
     * Check if any document within the collection has the attribute.
337
     *
338
     * @param string|array $attribute
339
     * @return Fluent
340
     */
341
    public function hasAttribute($attribute)
342
    {
343
        $parameters['handler'] = 'aql';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$parameters was never initialized. Although not strictly required by PHP, it is generally a good practice to add $parameters = array(); before regardless.
Loading history...
344
        $parameters['explanation'] = "Checking if any document within the collection has the '".implode(', ', (array) $attribute)."' attribute(s).";
345
        $parameters['attribute'] = $attribute;
346
347
        return $this->addCommand('hasAttribute', $parameters);
348
    }
349
350
    /**
351
     * Indicate that the given attributes should be renamed.
352
     *
353
     * @param  string  $from
354
     * @param  string  $to
355
     * @return Fluent
356
     */
357
    public function renameColumn($from, $to)
358
    {
359
        $parameters['handler'] = 'aql';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$parameters was never initialized. Although not strictly required by PHP, it is generally a good practice to add $parameters = array(); before regardless.
Loading history...
360
        $parameters['explanation'] = "Rename the attribute '$from' to '$to'.";
361
        $parameters['from'] = $from;
362
        $parameters['to'] = $to;
363
364
        return $this->addCommand('renameAttribute', $parameters);
365
    }
366
367
    /**
368
     * Indicate that the given index should be dropped.
369
     *
370
     * @param  string|array  $attributes
371
     * @param  string  $type
372
     * @return Fluent
373
     */
374
    public function dropIndex($attributes, $type)
375
    {
376
        $parameters['attributes'] = $attributes;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$parameters was never initialized. Although not strictly required by PHP, it is generally a good practice to add $parameters = array(); before regardless.
Loading history...
377
        $parameters['type'] = $type;
378
        $parameters['explanation'] = "Drop the '".$type."' index on [".implode(', ', (array) $attributes).'].';
379
        $parameters['handler'] = 'collection';
380
381
        return $this->addCommand('dropIndex', $parameters);
382
    }
383
384
    /**
385
     * Rename the collection to a given name.
386
     *
387
     * @param  string  $to
388
     * @return Fluent
389
     */
390
    public function rename($to)
391
    {
392
        return $this->addCommand('rename', compact('to'));
393
    }
394
395
    /**
396
     * Specify an index for the table.
397
     *
398
     * @param  string|array  $columns
399
     * @param  string  $name
400
     * @param  string|null  $algorithm
401
     * @return Fluent
402
     */
403
    public function index($columns = null, $name = null, $algorithm = null)
404
    {
405
        $type = $this->mapIndexType($algorithm);
406
407
        return $this->indexCommand($type, $columns);
408
    }
409
410
    /**
411
     * Create a hash index for fast exact matching.
412
     * Hash ! has ;).
413
     *
414
     * @param null $attributes
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $attributes is correct as it would always require null to be passed?
Loading history...
415
     * @param array $indexOptions
416
     * @return Fluent
417
     */
418
    public function hashIndex($attributes = null, $indexOptions = [])
419
    {
420
        return $this->indexCommand('hash', $attributes, $indexOptions);
421
    }
422
423
    /**
424
     * @param null|string $attribute
425
     * @param array $indexOptions
426
     * @return Fluent
427
     */
428
    public function fulltextIndex($attribute = null, $indexOptions = [])
429
    {
430
        return $this->indexCommand('fulltext', $attribute, $indexOptions);
431
    }
432
433
    /**
434
     *  Specify a spatial index for the collection.
435
     *
436
     * @param $attributes
437
     * @param array $indexOptions
438
     * @return Fluent
439
     */
440
    public function geoIndex($attributes, $indexOptions = [])
441
    {
442
        return $this->indexCommand('geo', $attributes, $indexOptions);
443
    }
444
445
    /**
446
     * Specify a spatial index for the table.
447
     * Alias for geoIndex().
448
     * @param  string|array  $columns
449
     * @param  string  $name
450
     * @return Fluent
451
     */
452
    public function spatialIndex($columns, $name = null)
453
    {
454
        return $this->geoIndex($columns);
455
    }
456
457
    public function skiplistIndex($attributes, $indexOptions = [])
458
    {
459
        return $this->indexCommand('skiplist', $attributes, $indexOptions);
460
    }
461
462
    /**
463
     * Create a TTL index for the table.
464
     * 
465
     * @param $attributes
466
     * @param array $indexOptions
467
     * @return Illuminate\Support\Fluent
0 ignored issues
show
Bug introduced by
The type LaravelFreelancerNL\Aran...luminate\Support\Fluent was not found. Did you mean Illuminate\Support\Fluent? If so, make sure to prefix the type with \.
Loading history...
468
     */
469
    public function ttlIndex($attributes, $indexOptions = [])
470
    {
471
        return $this->indexCommand('ttl', $attributes, $indexOptions);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->indexComma...ributes, $indexOptions) returns the type Illuminate\Support\Fluent which is incompatible with the documented return type LaravelFreelancerNL\Aran...luminate\Support\Fluent.
Loading history...
472
    }
473
474
    /**
475
     * Specify a unique index for the table.
476
     *
477
     * @param  string|array  $columns
478
     * @param  string  $name
479
     * @param  string|null  $algorithm
480
     * @return Fluent
481
     */
482
    public function unique($columns = null, $name = null, $algorithm = null)
483
    {
484
        $type = $this->mapIndexType($algorithm);
485
486
        $indexOptions['unique'] = true;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$indexOptions was never initialized. Although not strictly required by PHP, it is generally a good practice to add $indexOptions = array(); before regardless.
Loading history...
487
488
        return $this->indexCommand($type, $columns, $indexOptions);
489
    }
490
491
    /**
492
     * Add a new index command to the blueprint.
493
     *
494
     * @param  string  $type
495
     * @param  string|array  $attributes
496
     * @param  array $indexOptions
497
     * @return Fluent
498
     */
499
    protected function indexCommand($type = '', $attributes = [], $indexOptions = [])
500
    {
501
        if ($type == '') {
502
            $type = 'skiplist';
503
        }
504
505
        if ($attributes === null) {
0 ignored issues
show
introduced by
The condition $attributes === null is always false.
Loading history...
506
            $attributes = end($this->attributes);
507
        }
508
509
        if (is_string($attributes)) {
510
            $attributes = [$attributes];
511
        }
512
513
        $unique = false;
514
        if (isset($indexOptions['unique'])) {
515
            $unique = $indexOptions['unique'];
516
            unset($indexOptions['unique']);
517
        }
518
519
        return $this->addCommand('index', compact('type', 'attributes', 'unique', 'indexOptions'));
520
    }
521
522
    /**
523
     * Add a new command to the blueprint.
524
     *
525
     * @param  string  $name
526
     * @param  array  $parameters
527
     * @return Fluent
528
     */
529
    protected function addCommand($name, array $parameters = [])
530
    {
531
        $this->commands[] = $command = $this->createCommand($name, $parameters);
532
533
        return $command;
534
    }
535
536
    /**
537
     * Create a new Fluent command.
538
     *
539
     * @param  string  $name
540
     * @param  array  $parameters
541
     * @return Fluent
542
     */
543
    protected function createCommand($name, array $parameters = [])
544
    {
545
        return new Fluent(array_merge(compact('name'), $parameters));
546
    }
547
548
    /**
549
     * Get the collection the blueprint describes.
550
     *
551
     * @return string
552
     */
553
    public function getCollection()
554
    {
555
        return $this->collection;
556
    }
557
558
    /**
559
     * Alias for getCollection.
560
     *
561
     * @return string
562
     */
563
    public function getTable()
564
    {
565
        return $this->getCollection();
566
    }
567
568
    /**
569
     * Get the commands on the blueprint.
570
     *
571
     * @return Fluent[]
572
     */
573
    public function getCommands()
574
    {
575
        return $this->commands;
576
    }
577
578
    /**
579
     * Silently catch unsupported schema methods. Store attributes for backwards compatible fluent index creation.
580
     *
581
     * @param $method
582
     * @param $args
583
     * @return Blueprint
584
     */
585
    public function __call($method, $args)
586
    {
587
        $columnMethods = [
588
            'bigIncrements', 'bigInteger', 'binary', 'boolean', 'char', 'date', 'dateTime', 'dateTimeTz', 'decimal',
589
            'double', 'enum', 'float', 'geometry', 'geometryCollection', 'increments', 'integer', 'ipAddress', 'json',
590
            'jsonb', 'lineString', 'longText', 'macAddress', 'mediumIncrements', 'mediumInteger', 'mediumText',
591
            'morphs', 'uuidMorphs', 'multiLineString', 'multiPoint', 'multiPolygon',
592
            'nullableMorphs', 'nullableUuidMorphs', 'nullableTimestamps', 'point', 'polygon', 'rememberToken',
593
            'set', 'smallIncrements', 'smallInteger', 'softDeletes', 'softDeletesTz', 'string',
594
            'text', 'time', 'timeTz', 'timestamp', 'timestampTz', 'timestamps', 'tinyIncrements', 'tinyInteger',
595
            'unsignedBigInteger', 'unsignedDecimal', 'unsignedInteger', 'unsignedMediumInteger', 'unsignedSmallInteger',
596
            'unsignedTinyInteger', 'uuid', 'year',
597
        ];
598
599
        if (in_array($method, $columnMethods)) {
600
            if (isset($args)) {
601
                $this->attributes[] = $args;
602
            }
603
        }
604
605
        $autoIncrementMethods = ['increments', 'autoIncrement'];
606
        if (in_array($method, $autoIncrementMethods)) {
607
            $this->autoIncrement = true;
608
        }
609
610
        $info['method'] = $method;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$info was never initialized. Although not strictly required by PHP, it is generally a good practice to add $info = array(); before regardless.
Loading history...
611
        $info['explanation'] = "'$method' is ignored; Aranguent Schema Blueprint doesn't support it.";
612
        $this->addCommand('ignore', $info);
613
614
        return $this;
615
    }
616
617
    public function mapIndexType($algorithm)
618
    {
619
        $typeConversion = [
620
            'HASH' => 'hash',
621
            'BTREE' => 'skiplist',
622
            'RTREE' => 'geo',
623
            'TTL' => 'ttl',
624
        ];
625
        $algorithm = strtoupper($algorithm);
626
627
        return (isset($typeConversion[$algorithm])) ? $typeConversion[$algorithm] : 'skiplist';
628
    }
629
}
630