Passed
Push — master ( a94746...d75d6c )
by Bas
03:31
created

Grammar   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 506
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 132
dl 0
loc 506
rs 9.0399
c 0
b 0
f 0
wmc 42

26 Methods

Rating   Name   Duplication   Size   Complexity  
A compileUpdate() 0 13 1
A compileOffset() 0 5 1
A compileSum() 0 6 1
A compileDelete() 0 19 2
A getDateFormat() 0 3 1
A compileWheresToArray() 0 21 2
A compileInsert() 0 15 2
A generateTableAlias() 0 4 1
A compileInsertGetId() 0 3 1
A compileWheres() 0 15 3
A prefixTable() 0 3 1
A compileAvg() 0 6 1
A compileOrders() 0 8 2
A compileSelect() 0 20 1
A compileMin() 0 6 1
A compileLimit() 0 8 2
A compileAggregate() 0 4 1
A compileOrdersToArray() 0 5 1
A translateOperator() 0 6 2
A compileCount() 0 4 1
A compileColumns() 0 27 6
A compileFrom() 0 8 1
A compileComponents() 0 15 4
A prefixAlias() 0 3 1
A compileRandom() 0 3 1
A compileMax() 0 6 1

How to fix   Complexity   

Complex Class

Complex classes like Grammar often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Grammar, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace LaravelFreelancerNL\Aranguent\Query;
4
5
use Illuminate\Support\Str;
6
use Illuminate\Support\Traits\Macroable;
7
use LaravelFreelancerNL\FluentAQL\Exceptions\BindException as BindException;
8
use LaravelFreelancerNL\FluentAQL\Grammar as FluentAqlGrammar;
9
use LaravelFreelancerNL\FluentAQL\QueryBuilder as FluentAQL;
10
11
/*
12
 * Provides AQL syntax functions
13
 */
14
15
class Grammar extends FluentAqlGrammar
16
{
17
18
    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\Query\Grammar.
Loading history...
19
20
    /**
21
     * The grammar table prefix.
22
     *
23
     * @var string
24
     */
25
    protected $tablePrefix = '';
26
27
28
    /**
29
     * The grammar table prefix.
30
     *
31
     * @var null|int
32
     */
33
    protected $offset = null;
34
35
    /**
36
     * The components that make up a select clause.
37
     *
38
     * @var array
39
     */
40
    protected $selectComponents = [
41
        'from',
42
        'joins',
43
        'wheres',
44
        'groups',
45
        'aggregate',
46
        'havings',
47
        'orders',
48
        'offset',
49
        'limit',
50
        'columns',
51
    ];
52
53
54
    protected $operatorTranslations = [
55
        '=' => '==',
56
        '<>' => '!=',
57
        '<=>' => '==',
58
        'rlike' => '=~',
59
        'not rlike' => '!~',
60
        'regexp' => '=~',
61
        'not regexp' => '!~'
62
    ];
63
64
    /**
65
     * Get the format for database stored dates.
66
     *
67
     * @return string
68
     */
69
    public function getDateFormat()
70
    {
71
        return 'Y-m-d\TH:i:s.v\Z';
72
    }
73
74
    /**
75
     * @param Builder $builder
76
     * @param $table
77
     * @param string $postfix
78
     * @return mixed
79
     */
80
    protected function generateTableAlias($builder, $table, $postfix = 'Doc')
81
    {
82
        $builder->registerAlias($table, Str::singular($table).$postfix);
83
        return $builder;
84
    }
85
86
    protected function prefixTable($table)
87
    {
88
        return $this->tablePrefix.$table;
89
    }
90
91
    /**
92
     * Compile an insert statement into AQL.
93
     *
94
     * @param Builder $builder
95
     * @param array $values
96
     * @return Builder
97
     * @throws BindException
98
     */
99
    public function compileInsert(Builder $builder, array $values)
100
    {
101
        $table = $this->prefixTable($builder->from);
102
103
        if (empty($values)) {
104
            $builder->aqb = $builder->aqb->insert('{}', $table)->get();
105
            return $builder;
106
        }
107
108
        $builder->aqb = $builder->aqb->let('docs', $builder->aqb->bind($values))
109
            ->for('doc', 'docs')
110
            ->insert('doc', $table)
111
            ->return('NEW._key')
112
            ->get();
113
        return $builder;
114
    }
115
116
    /**
117
     * Compile an insert and get ID statement into SQL.
118
     *
119
     * @param Builder $builder
120
     * @param array $values
121
     * @return Builder
122
     * @throws BindException
123
     */
124
    public function compileInsertGetId(Builder $builder, $values)
125
    {
126
        return $this->compileInsert($builder, $values);
127
    }
128
129
    /**
130
     * Compile a select query into AQL.
131
     *
132
     * @param  Builder  $builder
133
     * @return Builder
134
     */
135
    public function compileSelect(Builder $builder)
136
    {
137
//        if ($builder->unions && $builder->aggregate) {
138
//            return $this->compileUnionAggregate($builder);
139
//        }
140
141
        // To compile the query, we'll spin through each component of the query and
142
        // see if that component exists. If it does we'll just call the compiler
143
        // function for the component which is responsible for making the SQL.
144
145
        $builder = $this->compileComponents($builder);
146
147
148
//        if ($builder->unions) {
149
//            $sql = $this->wrapUnion($sql).' '.$this->compileUnions($builder);
150
//        }
151
152
        $builder->aqb = $builder->aqb->get();
153
154
        return $builder;
155
    }
156
157
    /**
158
     * Compile the components necessary for a select clause.
159
     *
160
     * @param Builder $builder
161
     * @return Builder
162
     */
163
    protected function compileComponents(Builder $builder)
164
    {
165
        foreach ($this->selectComponents as $component) {
166
            // To compile the query, we'll spin through each component of the query and
167
            // see if that component exists. If it does we'll just call the compiler
168
            // function for the component which is responsible for making the SQL.
169
170
            if (isset($builder->$component) && ! is_null($builder->$component)) {
171
                $method = 'compile'.ucfirst($component);
172
173
                $builder = $this->$method($builder, $builder->$component);
174
            }
175
        }
176
177
        return $builder;
178
    }
179
180
181
    /**
182
     * Compile the "from" portion of the query -> FOR in AQL.
183
     *
184
     * @param Builder $builder
185
     * @param string $table
186
     * @return Builder
187
     */
188
    protected function compileFrom(Builder $builder, $table)
189
    {
190
        $table = $this->prefixTable($table);
191
        $builder = $this->generateTableAlias($builder, $table);
192
        $tableAlias = $builder->getAlias($table);
193
194
        $builder->aqb = $builder->aqb->for($tableAlias, $table);
195
         return $builder;
196
    }
197
198
    /**
199
     * Compile the "where" portions of the query.
200
     *
201
     * @param Builder $builder
202
     * @return Builder
203
     */
204
    protected function compileWheres(Builder $builder)
205
    {
206
        // Each type of where clauses has its own compiler function which is responsible
207
        // for actually creating the where clauses SQL. This helps keep the code nice
208
        // and maintainable since each clause has a very small method that it uses.
209
        if (is_null($builder->wheres)) {
0 ignored issues
show
introduced by
The condition is_null($builder->wheres) is always false.
Loading history...
210
            return $builder;
211
        }
212
213
        if (count($predicates = $this->compileWheresToArray($builder)) > 0) {
214
            $builder->aqb = $builder->aqb->filter($predicates);
0 ignored issues
show
Bug introduced by
$predicates of type array is incompatible with the type string expected by parameter $attribute of LaravelFreelancerNL\Flue...\QueryBuilder::filter(). ( Ignorable by Annotation )

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

214
            $builder->aqb = $builder->aqb->filter(/** @scrutinizer ignore-type */ $predicates);
Loading history...
215
            return $builder;
216
        }
217
218
        return $builder;
219
    }
220
221
    /**
222
     * Get an array of all the where clauses for the query.
223
     *
224
     * @param  \Illuminate\Database\Query\Builder  $builder
225
     * @return array
226
     */
227
    protected function compileWheresToArray($builder)
228
    {
229
        $result = collect($builder->wheres)->map(function ($where) use ($builder) {
230
            // ArangoDB uses a double '=' for comparison
231
            if ($where['operator'] == '=') {
232
                $where['operator'] = '==';
233
            }
234
            $where['operator'] = $this->translateOperator($where['operator']);
235
236
            //Prefix table alias on the column
237
            $where['column'] = $this->prefixAlias($builder, $builder->from, $where['column'] );
238
239
            return [
240
                $where['column'],
241
                $where['operator'],
242
                $where['value'],
243
                $where['boolean']
244
            ];
245
        })->all();
246
247
        return $result;
248
    }
249
250
    /**
251
     * Compile an aggregated select clause.
252
     *
253
     * @param  Builder  $builder
254
     * @param  array  $aggregate
255
     * @return Builder
256
     */
257
    protected function compileAggregate(Builder $builder, $aggregate)
258
    {
259
        $method = 'compile'.ucfirst($aggregate['function']);
260
        return $this->$method($builder, $aggregate);
261
262
    }
263
264
    /**
265
     * Compile AQL for count aggregate
266
     * @param Builder $builder
267
     * @param $aggregate
268
     * @return Builder
269
     */
270
    protected function compileCount(Builder $builder, $aggregate)
271
    {
272
        $builder->aqb = $builder->aqb->collect()->withCount('aggregateResult');
273
        return $builder;
274
    }
275
276
    /**
277
     * Compile AQL for max aggregate
278
     *
279
     * @param Builder $builder
280
     * @param $aggregate
281
     * @return Builder
282
     */
283
    protected function compileMax(Builder $builder, $aggregate)
284
    {
285
        $column = $this->prefixAlias($builder, $builder->from, $aggregate['columns'][0]);
286
287
        $builder->aqb = $builder->aqb->collect()->aggregate('aggregateResult', $builder->aqb->max($column));
288
        return $builder;
289
    }
290
291
    /**
292
     * Compile AQL for min aggregate
293
     *
294
     * @param Builder $builder
295
     * @param $aggregate
296
     * @return Builder
297
     */
298
    protected function compileMin(Builder $builder, $aggregate)
299
    {
300
        $column = $this->prefixAlias($builder, $builder->from, $aggregate['columns'][0]);
301
302
        $builder->aqb = $builder->aqb->collect()->aggregate('aggregateResult', $builder->aqb->min($column));
303
        return $builder;
304
    }
305
306
    /**
307
     * Compile AQL for average aggregate
308
     *
309
     * @param Builder $builder
310
     * @param $aggregate
311
     * @return Builder
312
     */
313
    protected function compileAvg(Builder $builder, $aggregate)
314
    {
315
        $column = $this->prefixAlias($builder, $builder->from, $aggregate['columns'][0]);
316
317
        $builder->aqb = $builder->aqb->collect()->aggregate('aggregateResult', $builder->aqb->average($column));
318
        return $builder;
319
    }
320
321
    /**
322
     * Compile AQL for sum aggregate
323
     *
324
     * @param Builder $builder
325
     * @param $aggregate
326
     * @return Builder
327
     */
328
    protected function compileSum(Builder $builder, $aggregate)
329
    {
330
        $column = $this->prefixAlias($builder, $builder->from, $aggregate['columns'][0]);
331
332
        $builder->aqb = $builder->aqb->collect()->aggregate('aggregateResult', $builder->aqb->sum($column));
333
        return $builder;
334
    }
335
336
    /**
337
     * Compile the "order by" portions of the query.
338
     *
339
     * @param Builder $builder
340
     * @param array $orders
341
     * @return Builder
342
     */
343
    protected function compileOrders(Builder $builder, $orders)
344
    {
345
        if (! empty($orders)) {
346
            $builder->aqb = $builder->aqb->sort($this->compileOrdersToArray($builder, $orders));
347
            return $builder;
348
        }
349
350
        return $builder;
351
    }
352
353
    /**
354
     * Compile the query orders to an array.
355
     *
356
     * @param  Builder  $builder
357
     * @param  array  $orders
358
     * @return array
359
     */
360
    protected function compileOrdersToArray(Builder $builder, $orders)
361
    {
362
        return array_map(function ($order) {
363
            return $order['sql'] ?? $this->prefixTable($order['column']).' '.$order['direction'];
364
        }, $orders);
365
    }
366
367
    /**
368
     * Compile the "offset" portions of the query.
369
     * We are handling this first by saving the offset which will be used by the FluentAQL's limit function
370
     *
371
     * @param Builder $builder
372
     * @param int $offset
373
     * @return Builder
374
     */
375
    protected function compileOffset(Builder $builder, $offset)
376
    {
377
        $this->offset = (int) $offset;
378
379
        return $builder;
380
    }
381
382
    /**
383
     * Compile the "limit" portions of the query.
384
     *
385
     * @param Builder $builder
386
     * @param int $limit
387
     * @return Builder
388
     */
389
    protected function compileLimit(Builder $builder, $limit)
390
    {
391
        if ($this->offset !== null) {
392
            $builder->aqb = $builder->aqb->limit((int)$this->offset, (int)$limit);
393
            return $builder;
394
        }
395
        $builder->aqb = $builder->aqb->limit((int) $limit);
396
        return $builder;
397
    }
398
399
400
    /**
401
     * Compile the "RETURN" portion of the query.
402
     *
403
     * @param Builder $builder
404
     * @param array $columns
405
     * @return Builder
406
     */
407
    protected function compileColumns(Builder $builder, array $columns) : Builder
408
    {
409
        // If the query is actually performing an aggregating select, we will let that
410
        // compiler handle the building of the select clauses, as it will need some
411
        // more syntax that is best handled by that function to keep things neat.
412
//        if (! is_null($builder->aggregate)) {
413
//            return;
414
//        }
415
416
        $values = [];
417
418
        $doc = $builder->getAlias($builder->from);
419
        foreach ($columns as $column) {
420
            if ($column != null && $column != '*') {
421
                $values[$column] = $doc.'.'.$column;
422
            }
423
        }
424
        if ($builder->aggregate !== null) {
425
            $values = 'aggregateResult';
426
        }
427
        if (empty($values)) {
428
            $values = $doc;
429
        }
430
431
432
        $builder->aqb = $builder->aqb->return($values, (boolean) $builder->distinct);
433
        return $builder;
434
    }
435
436
    /**
437
     * Compile an update statement into SQL.
438
     *
439
     * @param Builder $builder
440
     * @param array $values
441
     * @return Builder
442
     */
443
    public function compileUpdate(Builder $builder, array $values)
444
    {
445
        $table = $this->prefixTable($builder->from);
446
        $builder = $this->generateTableAlias($builder, $table);
447
        $tableAlias = $builder->getAlias($table);
448
        $builder->aqb = $builder->aqb->for($tableAlias, $table);
449
450
        //Fixme: joins?
451
        $builder = $this->compileWheres($builder);
452
453
        $builder->aqb = $builder->aqb->update($tableAlias, $values, $table)->get();
454
455
        return $builder;
456
    }
457
458
    /**
459
     * Compile a delete statement into SQL.
460
     *
461
     * @param Builder $builder
462
     * @param null $_key
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $_key is correct as it would always require null to be passed?
Loading history...
463
     * @return Builder
464
     */
465
    public function compileDelete(Builder $builder, $_key = null)
466
    {
467
        $table = $this->prefixTable($builder->from);
468
        $builder = $this->generateTableAlias($builder, $table);
469
        $tableAlias = $builder->getAlias($table);
470
471
        if (! is_null($_key)) {
0 ignored issues
show
introduced by
The condition is_null($_key) is always true.
Loading history...
472
            $builder->aqb = $builder->aqb->remove((string) $_key, $table)->get();
473
            return $builder;
474
        }
475
476
        $builder->aqb = $builder->aqb->for($tableAlias, $table);
477
478
        //Fixme: joins?
479
        $builder = $this->compileWheres($builder);
480
481
        $builder->aqb = $builder->aqb->remove($tableAlias, $table)->get();
482
483
        return $builder;
484
    }
485
486
487
    /**
488
     * Compile the random statement into SQL.
489
     *
490
     * @param Builder $builder
491
     * @return string
492
     */
493
    public function compileRandom(Builder $builder)
494
    {
495
        return (string) $builder->aqb->rand();
496
    }
497
498
    /**
499
     * Translate sql operators to their AQL equivalent where possible.
500
     *
501
     * @param string $operator
502
     * @return mixed|string
503
     */
504
    private function translateOperator(string $operator)
505
    {
506
        if (isset($this->operatorTranslations[strtolower($operator)])) {
507
            $operator = $this->operatorTranslations[$operator];
508
        }
509
        return $operator;
510
    }
511
512
    /**
513
     * @param Builder $builder
514
     * @param string $target
515
     * @param string $value
516
     * @return Builder
517
     */
518
    protected function prefixAlias(Builder $builder, string $target, string $value) : string
519
    {
520
        return $builder->getAlias($target).'.'.$value;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $builder->getAlias($target) . '.' . $value returns the type string which is incompatible with the documented return type LaravelFreelancerNL\Aranguent\Query\Builder.
Loading history...
521
    }
522
}
523