Completed
Push — master ( 42aa60...7d653e )
by Bas
03:14
created

Grammar   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 536
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 139
dl 0
loc 536
rs 8.72
c 0
b 0
f 0
wmc 46

27 Methods

Rating   Name   Duplication   Size   Complexity  
A getDateFormat() 0 3 1
A compileUpdate() 0 13 1
A compileOffset() 0 5 1
A compileSum() 0 7 1
A compileDelete() 0 20 2
A compileWheresToArray() 0 21 2
A compileInsert() 0 21 3
A compileInsertGetId() 0 3 1
A generateTableAlias() 0 5 1
A compileWheres() 0 16 3
A prefixTable() 0 3 1
A compileAvg() 0 7 1
A compileOrders() 0 9 2
A compileSelect() 0 19 1
A compileMin() 0 7 1
A compileLimit() 0 10 2
A compileAggregate() 0 5 1
A compileOrdersToArray() 0 11 3
A translateOperator() 0 7 2
A getOperators() 0 3 1
A compileCount() 0 5 1
A compileColumns() 0 27 6
A compileFrom() 0 9 1
A compileComponents() 0 15 4
A prefixAlias() 0 3 1
A compileRandom() 0 3 1
A compileMax() 0 7 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\Expressions\FunctionExpression;
9
use LaravelFreelancerNL\FluentAQL\Grammar as FluentAqlGrammar;
10
11
12
/*
13
 * Provides AQL syntax functions
14
 */
15
16
class Grammar extends FluentAqlGrammar
17
{
18
    use Macroable;
19
20
    public $name;
21
22
    /**
23
     * The grammar table prefix.
24
     *
25
     * @var string
26
     */
27
    protected $tablePrefix = '';
28
29
    /**
30
     * The grammar table prefix.
31
     *
32
     * @var null|int
33
     */
34
    protected $offset = null;
35
36
    /**
37
     * The components that make up a select clause.
38
     *
39
     * @var array
40
     */
41
    protected $selectComponents = [
42
        'from',
43
        'joins',
44
        'wheres',
45
        'groups',
46
        'aggregate',
47
        'havings',
48
        'orders',
49
        'offset',
50
        'limit',
51
        'columns',
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
     * Get the grammar specific operators.
76
     *
77
     * @return array
78
     */
79
    public function getOperators()
80
    {
81
        return $this->comparisonOperators;
82
    }
83
84
    /**
85
     * @param Builder $builder
86
     * @param $table
87
     * @param string $postfix
88
     * @return mixed
89
     */
90
    protected function generateTableAlias($builder, $table, $postfix = 'Doc')
91
    {
92
        $builder->registerAlias($table, Str::singular($table).$postfix);
93
94
        return $builder;
95
    }
96
97
    protected function prefixTable($table)
98
    {
99
        return $this->tablePrefix.$table;
100
    }
101
102
    /**
103
     * Compile an insert statement into AQL.
104
     *
105
     * @param Builder $builder
106
     * @param array $values
107
     * @return Builder
108
     * @throws BindException
109
     */
110
    public function compileInsert(Builder $builder, array $values)
111
    {
112
        if (! is_array(reset($values))) {
113
            $values = [$values];
114
        }
115
116
        $table = $this->prefixTable($builder->from);
117
118
        if (empty($values)) {
119
            $builder->aqb = $builder->aqb->insert('{}', $table)->get();
120
121
            return $builder;
122
        }
123
124
        $builder->aqb = $builder->aqb->let('docs', $builder->aqb->bind($values))
125
            ->for('doc', 'docs')
126
            ->insert('doc', $table)
127
            ->return('NEW._key')
128
            ->get();
129
130
        return $builder;
131
    }
132
133
    /**
134
     * Compile an insert and get ID statement into SQL.
135
     *
136
     * @param Builder $builder
137
     * @param array $values
138
     * @return Builder
139
     * @throws BindException
140
     */
141
    public function compileInsertGetId(Builder $builder, $values)
142
    {
143
        return $this->compileInsert($builder, $values);
144
    }
145
146
    /**
147
     * Compile a select query into AQL.
148
     *
149
     * @param  Builder  $builder
150
     * @return Builder
151
     */
152
    public function compileSelect(Builder $builder)
153
    {
154
//        if ($builder->unions && $builder->aggregate) {
155
//            return $this->compileUnionAggregate($builder);
156
//        }
157
158
        // To compile the query, we'll spin through each component of the query and
159
        // see if that component exists. If it does we'll just call the compiler
160
        // function for the component which is responsible for making the SQL.
161
162
        $builder = $this->compileComponents($builder);
163
164
//        if ($builder->unions) {
165
//            $sql = $this->wrapUnion($sql).' '.$this->compileUnions($builder);
166
//        }
167
168
        $builder->aqb = $builder->aqb->get();
169
170
        return $builder;
171
    }
172
173
    /**
174
     * Compile the components necessary for a select clause.
175
     *
176
     * @param Builder $builder
177
     * @return Builder
178
     */
179
    protected function compileComponents(Builder $builder)
180
    {
181
        foreach ($this->selectComponents as $component) {
182
            // To compile the query, we'll spin through each component of the query and
183
            // see if that component exists. If it does we'll just call the compiler
184
            // function for the component which is responsible for making the SQL.
185
186
            if (isset($builder->$component) && ! is_null($builder->$component)) {
187
                $method = 'compile'.ucfirst($component);
188
189
                $builder = $this->$method($builder, $builder->$component);
190
            }
191
        }
192
193
        return $builder;
194
    }
195
196
    /**
197
     * Compile the "from" portion of the query -> FOR in AQL.
198
     *
199
     * @param Builder $builder
200
     * @param string $table
201
     * @return Builder
202
     */
203
    protected function compileFrom(Builder $builder, $table)
204
    {
205
        $table = $this->prefixTable($table);
206
        $builder = $this->generateTableAlias($builder, $table);
207
        $tableAlias = $builder->getAlias($table);
208
209
        $builder->aqb = $builder->aqb->for($tableAlias, $table);
210
211
        return $builder;
212
    }
213
214
    /**
215
     * Compile the "where" portions of the query.
216
     *
217
     * @param Builder $builder
218
     * @return Builder
219
     */
220
    protected function compileWheres(Builder $builder)
221
    {
222
        // Each type of where clauses has its own compiler function which is responsible
223
        // for actually creating the where clauses SQL. This helps keep the code nice
224
        // and maintainable since each clause has a very small method that it uses.
225
        if (is_null($builder->wheres)) {
0 ignored issues
show
introduced by
The condition is_null($builder->wheres) is always false.
Loading history...
226
            return $builder;
227
        }
228
229
        if (count($predicates = $this->compileWheresToArray($builder)) > 0) {
230
            $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

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