Passed
Push — next ( bbb9c5...d20d04 )
by Bas
07:19 queued 03:59
created

Blueprint::__call()   B

Complexity

Conditions 6
Paths 12

Size

Total Lines 32
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 6

Importance

Changes 0
Metric Value
cc 6
eloc 20
c 0
b 0
f 0
nc 12
nop 2
dl 0
loc 32
ccs 22
cts 22
cp 1
crap 6
rs 8.9777
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 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;
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 70
    public function __construct($table, $grammar, $schemaManager, Closure $callback = null, $prefix = '')
104
    {
105 70
        $this->table = $table;
106
107 70
        $this->grammar = $grammar;
108
109 70
        $this->schemaManager = $schemaManager;
110
111 70
        $this->keyGenerator = config('arangodb.schema.keyOptions.type');
112
113 70
        $this->prefix = $prefix;
114
115 70
        if (!is_null($callback)) {
116 65
            $callback($this);
117
        }
118
    }
119
120
    /**
121
     * Execute the blueprint against the database.
122
     *
123
     *
124
     * @return void
125
     */
126 70
    public function build(Connection $connection, Grammar $grammar = null)
127
    {
128 70
        $this->connection = $connection;
129
130 70
        if (!isset($grammar)) {
131
            $this->grammar = $connection->getSchemaGrammar();
132
        }
133
134 70
        foreach ($this->commands as $command) {
135 68
            if ($command->handler === 'aql') {
136 3
                $command = $this->compileAqlCommand($command);
137
            }
138
139 68
            $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 3
    public function compileAqlCommand(Fluent $command): Fluent
147
    {
148 3
        $compileMethod = 'compile' . ucfirst($command->name);
0 ignored issues
show
Bug introduced by
It seems like $command->name can also be of type null; however, parameter $string of ucfirst() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

148
        $compileMethod = 'compile' . ucfirst(/** @scrutinizer ignore-type */ $command->name);
Loading history...
149 3
        if (method_exists($this->grammar, $compileMethod)) {
150 3
            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 68
    public function executeCommand(Fluent $command): void
160
    {
161 68
        $executeNamedMethod = 'execute' . ucfirst($command->name) . 'Command';
0 ignored issues
show
Bug introduced by
It seems like $command->name can also be of type null; however, parameter $string of ucfirst() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

161
        $executeNamedMethod = 'execute' . ucfirst(/** @scrutinizer ignore-type */ $command->name) . 'Command';
Loading history...
162
163 68
        if (method_exists($this, $executeNamedMethod)) {
164 68
            $this->$executeNamedMethod($command);
165
166 68
            return;
167
        }
168
169 3
        $this->executeCommandByHandler($command);
170
    }
171
172 3
    protected function executeCommandByHandler(Fluent $command): void
173
    {
174 3
        if (!isset($command->handler)) {
175
            return;
176
        }
177 3
        $executeHandlerMethod = 'execute' . ucfirst($command->handler) . 'Command';
0 ignored issues
show
Bug introduced by
It seems like $command->handler can also be of type null; however, parameter $string of ucfirst() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

177
        $executeHandlerMethod = 'execute' . ucfirst(/** @scrutinizer ignore-type */ $command->handler) . 'Command';
Loading history...
178 3
        if (method_exists($this, $executeHandlerMethod)) {
179 3
            $this->$executeHandlerMethod($command);
180
        }
181
    }
182
183
    /**
184
     * Execute an AQL statement.
185
     */
186 3
    public function executeAqlCommand(Fluent $command)
187
    {
188 3
        $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)) {
0 ignored issues
show
Bug introduced by
It seems like $command->method can also be of type null; however, parameter $method of method_exists() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

199
        if (method_exists($this->schemaManager, /** @scrutinizer ignore-type */ $command->method)) {
Loading history...
200
            $this->schemaManager->{$command->method}($command);
201
        }
202
    }
203
204
    /**
205
     * Solely provides feedback to the developer in pretend mode.
206
     */
207 57
    public function executeIgnoreCommand(Fluent $command): void
208
    {
209 57
        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 68
    protected function addCommand($name, array $parameters = [])
221
    {
222 68
        $this->commands[] = $command = $this->createCommand($name, $parameters);
223
224 68
        return $command;
225
    }
226
227
    /**
228
     * Create a new Fluent command.
229
     *
230
     * @param  string  $name
231
     * @return Fluent
232
     */
233 68
    protected function createCommand($name, array $parameters = [])
234
    {
235 68
        return new Fluent(array_merge(compact('name'), $parameters));
236
    }
237
238
    /**
239
     * Get the commands on the blueprint.
240
     *
241
     * @return Fluent[]
242
     */
243 3
    public function getCommands()
244
    {
245 3
        return $this->commands;
246
    }
247
248 1
    public function from(int $startingValue)
249
    {
250 1
        $this->incrementOffset = $startingValue;
251
252 1
        $info = [];
253 1
        $info['method'] = 'from';
254 1
        $info['explanation'] = "The autoincrement offset has been set to {$startingValue}.";
255
256 1
        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 57
    public function __call($method, $args = [])
266
    {
267 57
        $columnMethods = [
268 57
            'bigIncrements', 'bigInteger', 'binary', 'boolean', 'char', 'date', 'dateTime', 'dateTimeTz', 'decimal',
269 57
            'double', 'enum', 'engine', 'float', 'foreignId', 'geometry', 'geometryCollection', 'increments', 'integer',
270 57
            'ipAddress', 'json', 'jsonb', 'lineString', 'longText', 'macAddress', 'mediumIncrements', 'mediumInteger',
271 57
            'mediumText', 'morphs', 'uuidMorphs', 'multiLineString', 'multiPoint', 'multiPolygon',
272 57
            'nullableMorphs', 'nullableUuidMorphs', 'nullableTimestamps', 'point', 'polygon', 'rememberToken',
273 57
            'set', 'smallIncrements', 'smallInteger', 'softDeletes', 'softDeletesTz', 'string',
274 57
            'text', 'time', 'timeTz', 'timestamp', 'timestampTz', 'timestamps', 'tinyIncrements', 'tinyInteger',
275 57
            'unsignedBigInteger', 'unsignedDecimal', 'unsignedInteger', 'unsignedMediumInteger', 'unsignedSmallInteger',
276 57
            'unsignedTinyInteger', 'uuid', 'year',
277 57
        ];
278
279 57
        if (in_array($method, $columnMethods)) {
280 55
            if (isset($args[0]) && is_string($args[0])) {
281 55
                $this->columns[] = $args[0];
282
            }
283
        }
284
285 57
        $autoIncrementMethods = ['increments', 'autoIncrement'];
286 57
        if (in_array($method, $autoIncrementMethods)) {
287 3
            $this->setKeyGenerator('autoincrement');
288
        }
289
290 57
        if ($method === 'uuid') {
291 1
            $this->setKeyGenerator('uuid');
292
        }
293
294 57
        $this->ignoreMethod($method);
295
296 57
        return $this;
297
    }
298
299 4
    protected function setKeyGenerator(string $generator = 'traditional'): void
300
    {
301 4
        $column = end($this->columns);
302 4
        if ($column === '_key' || $column === 'id') {
303 4
            $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...
304
        }
305
    }
306
307 57
    protected function ignoreMethod(string $method)
308
    {
309 57
        $info = [];
310 57
        $info['method'] = $method;
311 57
        $info['explanation'] = "'$method' is ignored; Aranguent Schema Blueprint doesn't support it.";
312 57
        $this->addCommand('ignore', $info);
313
    }
314
315 63
    public function renameIdField(mixed $fields)
316
    {
317 63
        return array_map(function ($value) {
318 63
            return $value === 'id' ? '_key' : $value;
319 63
        }, $fields);
320
    }
321
}
322