Passed
Push — master ( 78e027...fa2487 )
by Edward
02:26
created

QueryCallbackBuilder   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 404
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
wmc 47
eloc 249
dl 0
loc 404
rs 8.64
c 1
b 0
f 1

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A getQueryCallback() 0 7 2
A createReference() 0 6 1
A getQueryProperties() 0 7 2
A addMethodCall() 0 6 1
A getVarName() 0 3 1
A getReference() 0 7 2
A isDefinite() 0 3 1
A onBeginProduction() 0 3 1
A onStart() 0 4 1
F onFinishProduction() 0 257 29
A hasReference() 0 3 1
A onFinish() 0 30 2
A setReference() 0 7 2

How to fix   Complexity   

Complex Class

Complex classes like QueryCallbackBuilder 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 QueryCallbackBuilder, and based on these observations, apply Extract Interface, too.

1
<?php
2
declare(strict_types=1);
3
4
namespace Remorhaz\JSON\Path\Query;
5
6
use function array_map;
7
use function array_reverse;
8
use PhpParser\BuilderFactory;
9
use PhpParser\Node as PhpAstNode;
10
use PhpParser\Node\Arg;
11
use PhpParser\Node\Expr;
12
use PhpParser\Node\Expr\Assign;
13
use PhpParser\Node\Stmt\Expression;
14
use PhpParser\Node\Stmt\Return_;
15
use PhpParser\PrettyPrinter\Standard;
16
use Remorhaz\JSON\Data\Value\NodeValueInterface;
17
use Remorhaz\JSON\Path\Value\ValueListInterface;
18
use Remorhaz\JSON\Path\Runtime\RuntimeInterface;
19
use Remorhaz\UniLex\AST\AbstractTranslatorListener;
20
use Remorhaz\UniLex\AST\Node as QueryAstNode;
21
use Remorhaz\UniLex\Exception as UniLexException;
22
use Remorhaz\UniLex\Stack\PushInterface;
23
24
final class QueryCallbackBuilder extends AbstractTranslatorListener implements QueryCallbackBuilderInterface
25
{
26
27
    private const ARG_RUNTIME = 'runtime';
28
29
    private const ARG_INPUT = 'input';
30
31
    private $php;
32
33
    private $references = [];
34
35
    private $runtime;
36
37
    private $input;
38
39
    private $stmts = [];
40
41
    private $queryCallback;
42
43
    private $properties;
44
45
    public function __construct()
46
    {
47
        $this->php = new BuilderFactory;
48
    }
49
50
    public function getQueryCallback(): callable
51
    {
52
        if (isset($this->queryCallback)) {
53
            return $this->queryCallback;
54
        }
55
56
        throw new Exception\QueryCallbackNotFoundException;
57
    }
58
59
    /**
60
     * {@inheritDoc}
61
     *
62
     * @return bool
63
     * @deprecated
64
     */
65
    public function isDefinite(): bool
66
    {
67
        return $this->getQueryProperties()->isDefinite();
68
    }
69
70
    public function getQueryProperties(): QueryPropertiesInterface
71
    {
72
        if (isset($this->properties)) {
73
            return $this->properties;
74
        }
75
76
        throw new Exception\PropertiesNotFoundException;
77
    }
78
79
    public function onStart(QueryAstNode $node): void
80
    {
81
        $this->runtime = $this->php->var(self::ARG_RUNTIME);
82
        $this->input = $this->php->var(self::ARG_INPUT);
83
    }
84
85
    public function onFinish(): void
86
    {
87
        $runtimeParam = $this
88
            ->php
89
            ->param(self::ARG_RUNTIME)
90
            ->setType(RuntimeInterface::class)
91
            ->getNode();
92
        $inputParam = $this
93
            ->php
94
            ->param(self::ARG_INPUT)
95
            ->setType(NodeValueInterface::class)
96
            ->getNode();
97
        $stmts = array_map(
98
            function (PhpAstNode $stmt): PhpAstNode {
99
                return $stmt instanceof Expr ? new Expression($stmt): $stmt;
100
            },
101
            $this->stmts
102
        );
103
104
        $closure = new Expr\Closure(
105
            [
106
                'stmts' => $stmts,
107
                'returnType' => ValueListInterface::class,
108
                'params' => [$runtimeParam, $inputParam],
109
            ]
110
        );
111
        $return = new Return_($closure);
112
113
        $callbackCode = (new Standard)->prettyPrint([$return]);
114
        $this->queryCallback = eval($callbackCode);
115
    }
116
117
    public function onBeginProduction(QueryAstNode $node, PushInterface $stack): void
118
    {
119
        $stack->push(...array_reverse($node->getChildList()));
120
    }
121
122
    /**
123
     * @param QueryAstNode $node
124
     * @throws UniLexException
125
     */
126
    public function onFinishProduction(QueryAstNode $node): void
127
    {
128
        if ($this->hasReference($node)) {
129
            return;
130
        }
131
        switch ($node->getName()) {
132
            case QueryAstNodeType::GET_INPUT:
133
                $this->addMethodCall(
134
                    $node,
135
                    'getInput',
136
                    $this->input
137
                );
138
                break;
139
140
            case QueryAstNodeType::SET_OUTPUT:
141
                $this->properties = new QueryProperties(
142
                    $node->getAttribute('is_definite'),
143
                    $node->getAttribute('is_path')
144
                );
145
                $this->stmts[] = new Return_($this->getReference($node->getChild(0)));
146
                break;
147
148
            case QueryAstNodeType::CREATE_FILTER_CONTEXT:
149
                $this->addMethodCall(
150
                    $node,
151
                    'createFilterContext',
152
                    $this->getReference($node->getChild(0))
153
                );
154
                break;
155
156
            case QueryAstNodeType::SPLIT:
157
                $this->addMethodCall(
158
                    $node,
159
                    'split',
160
                    $this->getReference($node->getChild(0))
161
                );
162
                break;
163
164
            case QueryAstNodeType::EVALUATE:
165
                $this->addMethodCall(
166
                    $node,
167
                    'evaluate',
168
                    $this->getReference($node->getChild(0)),
169
                    $this->getReference($node->getChild(1))
170
                );
171
                break;
172
173
            case QueryAstNodeType::FILTER:
174
                $this->addMethodCall(
175
                    $node,
176
                    'filter',
177
                    $this->getReference($node->getChild(0)),
178
                    $this->getReference($node->getChild(1))
179
                );
180
                break;
181
182
            case QueryAstNodeType::EVALUATE_LOGICAL_OR:
183
                $this->addMethodCall(
184
                    $node,
185
                    'evaluateLogicalOr',
186
                    $this->getReference($node->getChild(0)),
187
                    $this->getReference($node->getChild(1))
188
                );
189
                break;
190
191
            case QueryAstNodeType::EVALUATE_LOGICAL_AND:
192
                $this->addMethodCall(
193
                    $node,
194
                    'evaluateLogicalAnd',
195
                    $this->getReference($node->getChild(0)),
196
                    $this->getReference($node->getChild(1))
197
                );
198
                break;
199
200
            case QueryAstNodeType::EVALUATE_LOGICAL_NOT:
201
                $this->addMethodCall(
202
                    $node,
203
                    'evaluateLogicalNot',
204
                    $this->getReference($node->getChild(0))
205
                );
206
                break;
207
208
            case QueryAstNodeType::CALCULATE_IS_EQUAL:
209
                $this->addMethodCall(
210
                    $node,
211
                    'calculateIsEqual',
212
                    $this->getReference($node->getChild(0)),
213
                    $this->getReference($node->getChild(1))
214
                );
215
                break;
216
217
            case QueryAstNodeType::CALCULATE_IS_GREATER:
218
                $this->addMethodCall(
219
                    $node,
220
                    'calculateIsGreater',
221
                    $this->getReference($node->getChild(0)),
222
                    $this->getReference($node->getChild(1))
223
                );
224
                break;
225
226
            case QueryAstNodeType::CALCULATE_IS_REGEXP:
227
                $this->addMethodCall(
228
                    $node,
229
                    'calculateIsRegExp',
230
                    $this->php->val($node->getAttribute('pattern')),
231
                    $this->getReference($node->getChild(0))
232
                );
233
                break;
234
235
            case QueryAstNodeType::FETCH_CHILDREN:
236
                $this->addMethodCall(
237
                    $node,
238
                    'fetchChildren',
239
                    $this->getReference($node->getChild(0)),
240
                    new Arg(
241
                        $this->getReference($node->getChild(1)),
242
                        false,
243
                        true
244
                    )
245
                );
246
                break;
247
248
            case QueryAstNodeType::FETCH_CHILDREN_DEEP:
249
                $this->addMethodCall(
250
                    $node,
251
                    'fetchChildrenDeep',
252
                    $this->getReference($node->getChild(0)),
253
                    new Arg(
254
                        $this->getReference($node->getChild(1)),
255
                        false,
256
                        true
257
                    )
258
                );
259
                break;
260
261
            case QueryAstNodeType::MATCH_ANY_CHILD:
262
                $this->addMethodCall(
263
                    $node,
264
                    'matchAnyChild',
265
                    $this->getReference($node->getChild(0))
266
                );
267
                break;
268
269
            case QueryAstNodeType::MATCH_PROPERTY_STRICTLY:
270
                $this->addMethodCall(
271
                    $node,
272
                    'matchPropertyStrictly',
273
                    $this->getReference($node->getChild(0)),
274
                );
275
                break;
276
277
            case QueryAstNodeType::MATCH_ELEMENT_STRICTLY:
278
                $this->addMethodCall(
279
                    $node,
280
                    'matchElementStrictly',
281
                    $this->getReference($node->getChild(0)),
282
                );
283
                break;
284
285
            case QueryAstNodeType::AGGREGATE:
286
                $this->addMethodCall(
287
                    $node,
288
                    'aggregate',
289
                    $this->php->val($node->getAttribute('name')),
290
                    $this->getReference($node->getChild(0)),
291
                );
292
                break;
293
294
            case QueryAstNodeType::POPULATE_LITERAL:
295
                $this->addMethodCall(
296
                    $node,
297
                    'populateLiteral',
298
                    $this->getReference($node->getChild(0)),
299
                    $this->getReference($node->getChild(1))
300
                );
301
                break;
302
303
            case QueryAstNodeType::POPULATE_LITERAL_ARRAY:
304
                $this->addMethodCall(
305
                    $node,
306
                    'populateLiteralArray',
307
                    $this->getReference($node->getChild(0)),
308
                    new Arg(
309
                        $this->getReference($node->getChild(1)),
310
                        false,
311
                        true
312
                    )
313
                );
314
                break;
315
316
            case QueryAstNodeType::POPULATE_INDEX_LIST:
317
                $this->addMethodCall(
318
                    $node,
319
                    'populateIndexList',
320
                    $this->getReference($node->getChild(0)),
321
                    ...array_map(
322
                        [$this->php, 'val'],
323
                        $node->getAttribute('indexList')
324
                    )
325
                );
326
                break;
327
328
            case QueryAstNodeType::POPULATE_INDEX_SLICE:
329
                $attributes = $node->getAttributeList();
330
                $this->addMethodCall(
331
                    $node,
332
                    'populateIndexSlice',
333
                    $this->getReference($node->getChild(0)),
334
                    $this->php->val($attributes['start'] ?? null),
335
                    $this->php->val($attributes['end'] ?? null),
336
                    $this->php->val($attributes['step'] ?? null)
337
                );
338
339
                break;
340
341
            case QueryAstNodeType::POPULATE_NAME_LIST:
342
                $this->addMethodCall(
343
                    $node,
344
                    'populateNameList',
345
                    $this->getReference($node->getChild(0)),
346
                    ...array_map(
347
                        [$this->php, 'val'],
348
                        $node->getAttribute('nameList')
349
                    )
350
                );
351
                break;
352
353
            case QueryAstNodeType::CREATE_SCALAR:
354
                $attributes = $node->getAttributeList();
355
                // TODO: allow accessing null attributes
356
                $value = $this->php->val($attributes['value'] ?? null);
357
                $this->addMethodCall(
358
                    $node,
359
                    'createScalar',
360
                    $value
361
                );
362
                break;
363
364
            case QueryAstNodeType::CREATE_ARRAY:
365
                // [ X:APPEND_TO_ARRAY ]
366
                $items = [];
367
                foreach ($node->getChildList() as $child) {
368
                    $items[] = $this->getReference($child);
369
                }
370
                $this->stmts[] = new Assign(
371
                    $this->createReference($node),
372
                    $this->php->val($items)
373
                );
374
                break;
375
376
            case QueryAstNodeType::APPEND_TO_ARRAY:
377
                // [ 0:<value> ]
378
                $this->setReference(
379
                    $node,
380
                    $this->getReference($node->getChild(0))
381
                );
382
                break;
383
        }
384
    }
385
386
    private function getVarName(QueryAstNode $node): string
387
    {
388
        return "var{$node->getId()}";
389
    }
390
391
    private function createReference(QueryAstNode $node): Expr
392
    {
393
        $reference = $this->php->var($this->getVarName($node));
394
        $this->setReference($node, $reference);
395
396
        return $reference;
397
    }
398
399
    private function setReference(QueryAstNode $node, Expr $expr): void
400
    {
401
        if (isset($this->references[$node->getId()])) {
402
            throw new Exception\ReferenceAlreadyExistsException($node->getId());
403
        }
404
405
        $this->references[$node->getId()] = $expr;
406
    }
407
408
    private function hasReference(QueryAstNode $node): bool
409
    {
410
        return isset($this->references[$node->getId()]);
411
    }
412
413
    private function getReference(QueryAstNode $node): Expr
414
    {
415
        if (!isset($this->references[$node->getId()])) {
416
            throw new Exception\ReferenceNotFoundException($node->getId());
417
        }
418
419
        return $this->references[$node->getId()];
420
    }
421
422
    private function addMethodCall(QueryAstNode $node, string $method, PhpAstNode ...$args): void
423
    {
424
        $methodCall = $this
425
            ->php
426
            ->methodCall($this->runtime, $method, $args);
427
        $this->stmts[] = new Assign($this->createReference($node), $methodCall);
428
    }
429
}
430