Completed
Push — master ( 113bfa...89e2e4 )
by Edward
07:06
created

CallbackBuilder   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 406
Duplicated Lines 0 %

Test Coverage

Coverage 30.51%

Importance

Changes 0
Metric Value
eloc 258
dl 0
loc 406
ccs 72
cts 236
cp 0.3051
rs 8.64
c 0
b 0
f 0
wmc 47

13 Methods

Rating   Name   Duplication   Size   Complexity  
A getCapabilities() 0 7 2
A onStart() 0 4 1
A onFinish() 0 30 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 addMethodCall() 0 6 1
A getVarName() 0 3 1
F onFinishProduction() 0 270 30
A getReference() 0 7 2

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