Completed
Push — master ( 37a545...2ac5c4 )
by Edward
04:29
created

CallbackBuilder   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 428
Duplicated Lines 0 %

Test Coverage

Coverage 31.45%

Importance

Changes 0
Metric Value
eloc 275
dl 0
loc 428
ccs 78
cts 248
cp 0.3145
rs 8.4
c 0
b 0
f 0
wmc 50

14 Methods

Rating   Name   Duplication   Size   Complexity  
A getCapabilities() 0 7 2
A onStart() 0 5 1
A onFinish() 0 35 2
A __construct() 0 3 1
A onBeginProduction() 0 3 1
A getCallback() 0 7 2
A hasReference() 0 3 1
A setReference() 0 7 2
A createReference() 0 6 1
A getVarName() 0 3 1
F onFinishProduction() 0 274 32
A addEvaluatorMethodCall() 0 6 1
A getReference() 0 7 2
A addRuntimeMethodCall() 0 6 1

How to fix   Complexity   

Complex Class

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