Failed Conditions
Push — chore/add-feature-tests ( 9637dd...e69874 )
by
unknown
13:44
created

Blueprint::setKeyGenerator()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 3
nc 2
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
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\Columns;
13
use LaravelFreelancerNL\Aranguent\Schema\Concerns\Indexes;
14
use LaravelFreelancerNL\Aranguent\Schema\Concerns\Tables;
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 (and can't) 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 Tables;
0 ignored issues
show
introduced by
The trait LaravelFreelancerNL\Aran...\Schema\Concerns\Tables requires some properties which are not provided by LaravelFreelancerNL\Aranguent\Schema\Blueprint: $options, $explanation
Loading history...
32
    use Columns;
33
    use Indexes;
0 ignored issues
show
introduced by
The trait LaravelFreelancerNL\Aran...Schema\Concerns\Indexes 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 = 'traditional';
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->prefix = $prefix;
112
113
        if (!is_null($callback)) {
114
            $callback($this);
115
        }
116
    }
117
118
    /**
119
     * Execute the blueprint against the database.
120
     *
121
     *
122
     * @return void
123
     */
124
    public function build(Connection $connection, Grammar $grammar = null)
125
    {
126
        $this->connection = $connection;
127
128
        if (!isset($grammar)) {
129
            $this->grammar = $connection->getSchemaGrammar();
130
        }
131
132
        foreach ($this->commands as $command) {
133
            if ($command->handler === 'aql') {
134
                $command = $this->compileAqlCommand($command);
135
            }
136
137
            $this->executeCommand($command);
138
        }
139
    }
140
141
    /**
142
     * Generate the compilation method name and call it if method exists in the Grammar object.
143
     */
144
    public function compileAqlCommand(Fluent $command): Fluent
145
    {
146
        $compileMethod = 'compile' . ucfirst($command->name);
147
        if (method_exists($this->grammar, $compileMethod)) {
148
            return $this->grammar->$compileMethod($this->table, $command);
149
        }
150
151
        return $command;
152
    }
153
154
    /**
155
     * Generate the execution method name and call it if the method exists.
156
     */
157
    public function executeCommand(Fluent $command): void
158
    {
159
        $executeNamedMethod = 'execute' . ucfirst($command->name) . 'Command';
160
161
        if (method_exists($this, $executeNamedMethod)) {
162
            $this->$executeNamedMethod($command);
163
164
            return;
165
        }
166
167
        $this->executeCommandByHandler($command);
168
    }
169
170
    protected function executeCommandByHandler(Fluent $command): void
171
    {
172
        if (!isset($command->handler)) {
173
            return;
174
        }
175
        $executeHandlerMethod = 'execute' . ucfirst($command->handler) . 'Command';
176
        if (method_exists($this, $executeHandlerMethod)) {
177
            $this->$executeHandlerMethod($command);
178
        }
179
    }
180
181
    /**
182
     * Execute an AQL statement.
183
     */
184
    public function executeAqlCommand(Fluent $command)
185
    {
186
        $this->connection->statement($command->aqb->query, $command->aqb->binds);
187
    }
188
189
    public function executeCollectionCommand(Fluent $command): void
190
    {
191
        if ($this->connection->pretending()) {
192
            $this->connection->logQuery('/* ' . $command->explanation . " */\n", []);
193
194
            return;
195
        }
196
197
        if (method_exists($this->schemaManager, $command->method)) {
198
            $this->schemaManager->{$command->method}($command);
199
        }
200
    }
201
202
    /**
203
     * Solely provides feedback to the developer in pretend mode.
204
     */
205
    public function executeIgnoreCommand(Fluent $command): void
206
    {
207
        if ($this->connection->pretending()) {
208
            $this->connection->logQuery('/* ' . $command->explanation . " */\n", []);
209
        }
210
    }
211
212
    /**
213
     * Add a new command to the blueprint.
214
     *
215
     * @param  string  $name
216
     * @return Fluent
217
     */
218
    protected function addCommand($name, array $parameters = [])
219
    {
220
        $this->commands[] = $command = $this->createCommand($name, $parameters);
221
222
        return $command;
223
    }
224
225
    /**
226
     * Create a new Fluent command.
227
     *
228
     * @param  string  $name
229
     * @return Fluent
230
     */
231
    protected function createCommand($name, array $parameters = [])
232
    {
233
        return new Fluent(array_merge(compact('name'), $parameters));
234
    }
235
236
    /**
237
     * Get the commands on the blueprint.
238
     *
239
     * @return Fluent[]
240
     */
241
    public function getCommands()
242
    {
243
        return $this->commands;
244
    }
245
246
    public function from(int $startingValue)
247
    {
248
        $this->incrementOffset = $startingValue;
249
250
        $info['method'] = 'from';
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...
251
        $info['explanation'] = "The autoincrement offset has been set to {$startingValue}.";
252
253
        return $this->addCommand('ignore', $info);
254
    }
255
256
    /**
257
     * Silently catch unsupported schema methods. Store columns for backwards compatible fluent index creation.
258
     *
259
     * @param  array<mixed>  $args
260
     * @return Blueprint
261
     */
262
    public function __call($method, $args = [])
263
    {
264
        $columnMethods = [
265
            'bigIncrements', 'bigInteger', 'binary', 'boolean', 'char', 'date', 'dateTime', 'dateTimeTz', 'decimal',
266
            'double', 'enum', 'engine', 'float', 'foreignId', 'geometry', 'geometryCollection', 'increments', 'integer',
267
            'ipAddress', 'json', 'jsonb', 'lineString', 'longText', 'macAddress', 'mediumIncrements', 'mediumInteger',
268
            'mediumText', 'morphs', 'uuidMorphs', 'multiLineString', 'multiPoint', 'multiPolygon',
269
            'nullableMorphs', 'nullableUuidMorphs', 'nullableTimestamps', 'point', 'polygon', 'rememberToken',
270
            'set', 'smallIncrements', 'smallInteger', 'softDeletes', 'softDeletesTz', 'string',
271
            'text', 'time', 'timeTz', 'timestamp', 'timestampTz', 'timestamps', 'tinyIncrements', 'tinyInteger',
272
            'unsignedBigInteger', 'unsignedDecimal', 'unsignedInteger', 'unsignedMediumInteger', 'unsignedSmallInteger',
273
            'unsignedTinyInteger', 'uuid', 'year',
274
        ];
275
276
        if (in_array($method, $columnMethods)) {
277
            if (isset($args[0]) && is_string($args[0])) {
278
                $this->columns[] = $args[0];
279
            }
280
        }
281
282
        $autoIncrementMethods = ['increments', 'autoIncrement'];
283
        if (in_array($method, $autoIncrementMethods)) {
284
            $this->setKeyGenerator('autoincrement');
285
        }
286
287
        if ($method === 'uuid') {
288
            $this->setKeyGenerator('uuid');
289
        }
290
291
        $this->ignoreMethod($method);
292
293
        return $this;
294
    }
295
296
    protected function setKeyGenerator(string $generator = 'traditional'): void
297
    {
298
        $column = end($this->columns);
299
        if ($column === '_key' || $column === 'id') {
300
            $this->keyGenerator = $generator;
0 ignored issues
show
Documentation Bug introduced by
The property $keyGenerator was declared of type boolean, but $generator is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
301
        }
302
    }
303
304
    protected function ignoreMethod(string $method)
305
    {
306
        $info = [];
307
        $info['method'] = $method;
308
        $info['explanation'] = "'$method' is ignored; Aranguent Schema Blueprint doesn't support it.";
309
        $this->addCommand('ignore', $info);
310
    }
311
312
    public function renameIdField(mixed $fields)
313
    {
314
        return array_map(function ($value) {
315
            return $value === 'id' ? '_key' : $value;
316
        }, $fields);
317
    }
318
}
319