Blueprint   A
last analyzed

Complexity

Total Complexity 35

Size/Duplication

Total Lines 302
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 93
dl 0
loc 302
rs 9.6
c 1
b 0
f 0
wmc 35

16 Methods

Rating   Name   Duplication   Size   Complexity  
A addCommand() 0 5 1
A executeIgnoreCommand() 0 4 2
A executeCommand() 0 11 2
A compileAqlCommand() 0 8 2
A executeAqlCommand() 0 3 1
A __construct() 0 14 2
A createCommand() 0 3 1
A executeCollectionCommand() 0 10 3
A build() 0 14 4
A executeCommandByHandler() 0 8 3
A getCommands() 0 3 1
A from() 0 9 1
A __call() 0 28 5
A ignoreMethod() 0 6 1
A renameIdField() 0 5 2
A setKeyOptions() 0 16 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace LaravelFreelancerNL\Aranguent\Schema;
6
7
use ArangoClient\Schema\SchemaManager;
8
use Closure;
9
use Illuminate\Support\Fluent;
10
use Illuminate\Support\Traits\Macroable;
11
use LaravelFreelancerNL\Aranguent\Connection;
12
use LaravelFreelancerNL\Aranguent\Schema\Concerns\ColumnCommands;
13
use LaravelFreelancerNL\Aranguent\Schema\Concerns\IndexCommands;
14
use LaravelFreelancerNL\Aranguent\Schema\Concerns\TableCommands;
15
16
/**
17
 * Class Blueprint.
18
 *
19
 * The Schema blueprint works differently from the standard Illuminate version:
20
 * 1) ArangoDB is schemaless: we don't need to create columns
21
 * 2) ArangoDB doesn't allow DB schema actions within AQL nor within a transaction.
22
 *
23
 * This means that:
24
 * 1) We catch column related methods silently for backwards compatibility and ease of migrating between DB types
25
 * 2) We don't need to compile AQL for transactions within the accompanying schema grammar. (for now)
26
 * 3) We can just execute each command on order. We will gather them first for possible future optimisations.
27
 */
28
class Blueprint
29
{
30
    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...
31
    use TableCommands;
0 ignored issues
show
introduced by
The trait LaravelFreelancerNL\Aran...\Concerns\TableCommands requires some properties which are not provided by LaravelFreelancerNL\Aranguent\Schema\Blueprint: $options, $explanation
Loading history...
32
    use ColumnCommands;
33
    use IndexCommands;
0 ignored issues
show
introduced by
The trait LaravelFreelancerNL\Aran...\Concerns\IndexCommands requires some properties which are not provided by LaravelFreelancerNL\Aranguent\Schema\Blueprint: $indexOptions, $id, $explanation, $type, $unique, $index
Loading history...
34
35
    /**
36
     * The connection that is used by the blueprint.
37
     *
38
     * @var Connection
39
     */
40
    protected $connection;
41
42
    /**
43
     * The grammar that is used by the blueprint.
44
     *
45
     * @var Grammar
46
     */
47
    protected $grammar;
48
49
    /**
50
     * The table the blueprint describes.
51
     *
52
     * @var string
53
     */
54
    protected $table;
55
56
    /**
57
     * The handler for table manipulation.
58
     */
59
    protected SchemaManager $schemaManager;
60
61
    /**
62
     * The prefix of the table.
63
     *
64
     * @var string
65
     */
66
    protected $prefix;
67
68
    /**
69
     * The commands that should be run for the table.
70
     *
71
     * @var Fluent[]
72
     */
73
    protected $commands = [];
74
75
    /**
76
     * Catching columns to be able to add fluent indexes.
77
     *
78
     * @var array
79
     */
80
    protected $columns = [];
81
82
83
    /**
84
     * the key generator to use for this table.
85
     *
86
     * @var bool
87
     */
88
    protected $keyGenerator;
89
90
    protected int $incrementOffset = 0;
91
92
    /**
93
     * Create a new schema blueprint.
94
     *
95
     * Blueprint constructor.
96
     *
97
     * @param string $table
98
     * @param Grammar $grammar
99
     * @param SchemaManager $schemaManager
100
     * @param Closure|null $callback
101
     * @param string $prefix
102
     */
103
    public function __construct($table, $grammar, $schemaManager, Closure $callback = null, $prefix = '')
104
    {
105
        $this->table = $table;
106
107
        $this->grammar = $grammar;
108
109
        $this->schemaManager = $schemaManager;
110
111
        $this->keyGenerator = config('arangodb.schema.keyOptions.type');
112
113
        $this->prefix = $prefix;
114
115
        if (!is_null($callback)) {
116
            $callback($this);
117
        }
118
    }
119
120
    /**
121
     * Execute the blueprint against the database.
122
     *
123
     *
124
     * @return void
125
     */
126
    public function build(Connection $connection, Grammar $grammar = null)
127
    {
128
        $this->connection = $connection;
129
130
        if (!isset($grammar)) {
131
            $this->grammar = $connection->getSchemaGrammar();
132
        }
133
134
        foreach ($this->commands as $command) {
135
            if ($command->handler === 'aql') {
136
                $command = $this->compileAqlCommand($command);
137
            }
138
139
            $this->executeCommand($command);
140
        }
141
    }
142
143
    /**
144
     * Generate the compilation method name and call it if method exists in the Grammar object.
145
     */
146
    public function compileAqlCommand(Fluent $command): Fluent
147
    {
148
        $compileMethod = 'compile' . ucfirst($command->name);
149
        if (method_exists($this->grammar, $compileMethod)) {
150
            return $this->grammar->$compileMethod($this->table, $command);
151
        }
152
153
        return $command;
154
    }
155
156
    /**
157
     * Generate the execution method name and call it if the method exists.
158
     */
159
    public function executeCommand(Fluent $command): void
160
    {
161
        $executeNamedMethod = 'execute' . ucfirst($command->name) . 'Command';
162
163
        if (method_exists($this, $executeNamedMethod)) {
164
            $this->$executeNamedMethod($command);
165
166
            return;
167
        }
168
169
        $this->executeCommandByHandler($command);
170
    }
171
172
    protected function executeCommandByHandler(Fluent $command): void
173
    {
174
        if (!isset($command->handler)) {
175
            return;
176
        }
177
        $executeHandlerMethod = 'execute' . ucfirst($command->handler) . 'Command';
178
        if (method_exists($this, $executeHandlerMethod)) {
179
            $this->$executeHandlerMethod($command);
180
        }
181
    }
182
183
    /**
184
     * Execute an AQL statement.
185
     */
186
    public function executeAqlCommand(Fluent $command)
187
    {
188
        $this->connection->statement($command->aqb->query, $command->aqb->binds);
189
    }
190
191
    public function executeCollectionCommand(Fluent $command): void
192
    {
193
        if ($this->connection->pretending()) {
194
            $this->connection->logQuery('/* ' . $command->explanation . " */\n", []);
195
196
            return;
197
        }
198
199
        if (method_exists($this->schemaManager, $command->method)) {
200
            $this->schemaManager->{$command->method}($command);
201
        }
202
    }
203
204
    /**
205
     * Solely provides feedback to the developer in pretend mode.
206
     */
207
    public function executeIgnoreCommand(Fluent $command): void
208
    {
209
        if ($this->connection->pretending()) {
210
            $this->connection->logQuery('/* ' . $command->explanation . " */\n", []);
211
        }
212
    }
213
214
    /**
215
     * Add a new command to the blueprint.
216
     *
217
     * @param  string  $name
218
     * @return Fluent
219
     */
220
    protected function addCommand($name, array $parameters = [])
221
    {
222
        $this->commands[] = $command = $this->createCommand($name, $parameters);
223
224
        return $command;
225
    }
226
227
    /**
228
     * Create a new Fluent command.
229
     *
230
     * @param  string  $name
231
     * @return Fluent
232
     */
233
    protected function createCommand($name, array $parameters = [])
234
    {
235
        return new Fluent(array_merge(compact('name'), $parameters));
236
    }
237
238
    /**
239
     * Get the commands on the blueprint.
240
     *
241
     * @return Fluent[]
242
     */
243
    public function getCommands()
244
    {
245
        return $this->commands;
246
    }
247
248
    public function from(int $startingValue)
249
    {
250
        $this->incrementOffset = $startingValue;
251
252
        $info = [];
253
        $info['method'] = 'from';
254
        $info['explanation'] = "The autoincrement offset has been set to {$startingValue}.";
255
256
        return $this->addCommand('ignore', $info);
257
    }
258
259
    /**
260
     * Silently catch unsupported schema methods. Store columns for backwards compatible fluent index creation.
261
     *
262
     * @param  array<mixed>  $args
263
     * @return Blueprint
264
     */
265
    public function __call($method, $args = [])
266
    {
267
        $columnMethods = [
268
            'bigIncrements', 'bigInteger', 'binary', 'boolean', 'char', 'date', 'dateTime', 'dateTimeTz', 'decimal',
269
            'double', 'enum', 'engine', 'float', 'foreignId', 'geometry', 'geometryCollection', 'increments', 'integer',
270
            'ipAddress', 'json', 'jsonb', 'lineString', 'longText', 'macAddress', 'mediumIncrements', 'mediumInteger',
271
            'mediumText', 'morphs', 'uuidMorphs', 'multiLineString', 'multiPoint', 'multiPolygon',
272
            'nullableMorphs', 'nullableUuidMorphs', 'nullableTimestamps', 'point', 'polygon', 'rememberToken',
273
            'set', 'smallIncrements', 'smallInteger', 'softDeletes', 'softDeletesTz', 'string',
274
            'text', 'time', 'timeTz', 'timestamp', 'timestampTz', 'timestamps', 'tinyIncrements', 'tinyInteger',
275
            'unsignedBigInteger', 'unsignedDecimal', 'unsignedInteger', 'unsignedMediumInteger', 'unsignedSmallInteger',
276
            'unsignedTinyInteger', 'uuid', 'year',
277
        ];
278
279
        if (in_array($method, $columnMethods)) {
280
            if (isset($args[0]) && is_string($args[0])) {
281
                $this->columns[] = $args[0];
282
            }
283
        }
284
285
        $keyMethods = ['autoIncrement', 'bigIncrements', 'increments', 'mediumIncrements', 'tinyIncrements', 'uuid'];
286
        if (in_array($method, $keyMethods)) {
287
            $this->handleKeyCommands($method, $args);
288
        }
289
290
        $this->ignoreMethod($method);
291
292
        return $this;
293
    }
294
295
    protected function ignoreMethod(string $method)
296
    {
297
        $info = [];
298
        $info['method'] = $method;
299
        $info['explanation'] = "'$method' is ignored; Aranguent Schema Blueprint doesn't support it.";
300
        $this->addCommand('ignore', $info);
301
    }
302
303
    public function renameIdField(mixed $fields)
304
    {
305
        return array_map(function ($value) {
306
            return $value === 'id' ? '_key' : $value;
307
        }, $fields);
308
    }
309
310
    /**
311
     * @param mixed[] $options
312
     * @return mixed[]
313
     */
314
    protected function setKeyOptions($tableOptions)
315
    {
316
        $configuredKeyOptions = config('arangodb.schema.keyOptions');
317
318
        $columnOptions = [];
319
        $columnOptions['type'] = $this->keyGenerator;
320
321
        $mergedKeyOptions = (config('arangodb.schema.key_handling.prioritize_configured_key_type'))
322
            ? array_merge($columnOptions, $configuredKeyOptions, $tableOptions)
323
            : array_merge($configuredKeyOptions, $columnOptions, $tableOptions);
324
325
        if ($mergedKeyOptions['type'] === 'autoincrement' && $this->incrementOffset !== 0) {
326
            $mergedKeyOptions['offset'] = $this->incrementOffset;
327
        }
328
329
        return $mergedKeyOptions;
330
    }
331
}
332