Passed
Push — master ( 4f498a...34f509 )
by Edward
05:42
created

CallbackBuilder::onFinishProduction()   F

Complexity

Conditions 30
Paths 28

Size

Total Lines 279
Code Lines 201

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 776.8101

Importance

Changes 0
Metric Value
cc 30
eloc 201
nc 28
nop 1
dl 0
loc 279
ccs 12
cts 199
cp 0.0603
crap 776.8101
rs 3.3333
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Remorhaz\JSON\Path\Query;
6
7
use PhpParser\BuilderFactory;
8
use PhpParser\Node as PhpAstNode;
9
use PhpParser\Node\Arg;
10
use PhpParser\Node\Expr;
11
use PhpParser\Node\Expr\Assign;
12
use PhpParser\Node\Stmt\Expression;
13
use PhpParser\Node\Stmt\Return_;
14
use PhpParser\PrettyPrinter\Standard;
15
use Remorhaz\JSON\Path\Runtime\EvaluatorInterface;
16
use Remorhaz\JSON\Path\Runtime\LiteralFactoryInterface;
17
use Remorhaz\JSON\Path\Runtime\Matcher\MatcherFactoryInterface;
18
use Remorhaz\JSON\Path\Runtime\ValueListFetcherInterface;
19
use Remorhaz\JSON\Path\Value\NodeValueListInterface;
20
use Remorhaz\JSON\Path\Value\ValueListInterface;
21
use Remorhaz\UniLex\AST\AbstractTranslatorListener;
22
use Remorhaz\UniLex\AST\Node as QueryAstNode;
23
use Remorhaz\UniLex\Exception as UniLexException;
24
use Remorhaz\UniLex\Stack\PushInterface;
25
26
use function array_map;
27
use function array_reverse;
28
use function is_callable;
29
30
final class CallbackBuilder extends AbstractTranslatorListener implements CallbackBuilderInterface
31
{
32
33
    private const ARG_INPUT = 'input';
34
35
    private const ARG_VALUE_LIST_FETCHER = 'valueListFetcher';
36
37
    private const ARG_EVALUATOR = 'evaluator';
38
39
    private const ARG_LITERAL_FACTORY = 'literalFactory';
40
41
    private const ARG_MATCHER_FACTORY = 'matcherFactory';
42
43
    private $php;
44
45
    private $references = [];
46
47
    private $input;
48
49
    private $valueListFetcher;
50
51
    private $evaluator;
52
53
    private $literalFactory;
54
55
    private $matcherFactory;
56
57
    private $stmts = [];
58
59
    private $callback;
60
61
    private $callbackCode;
62
63
    private $capabilities;
64
65 6
    public function __construct()
66
    {
67 6
        $this->php = new BuilderFactory();
68 6
    }
69
70
    public function getCallback(): callable
71
    {
72
        if (!isset($this->callback)) {
73
            $callback = eval($this->getCallbackCode());
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
74
            if (!is_callable($callback)) {
75
                throw new Exception\InvalidCallbackCodeException($this->getCallbackCode());
76
            }
77
            $this->callback = $callback;
78
        }
79
80
        return $this->callback;
81
    }
82
83 1
    public function getCallbackCode(): string
84
    {
85 1
        if (isset($this->callbackCode)) {
86
            return $this->callbackCode;
87
        }
88
89 1
        throw new Exception\QueryCallbackCodeNotFoundException();
90
    }
91
92 3
    public function getCapabilities(): CapabilitiesInterface
93
    {
94 3
        if (isset($this->capabilities)) {
95 2
            return $this->capabilities;
96
        }
97
98 1
        throw new Exception\CapabilitiesNotFoundException();
99
    }
100
101 3
    public function onStart(QueryAstNode $node): void
102
    {
103 3
        $this->input = $this->php->var(self::ARG_INPUT);
104 3
        $this->valueListFetcher = $this->php->var(self::ARG_VALUE_LIST_FETCHER);
105 3
        $this->evaluator = $this->php->var(self::ARG_EVALUATOR);
106 3
        $this->literalFactory = $this->php->var(self::ARG_LITERAL_FACTORY);
107 3
        $this->matcherFactory = $this->php->var(self::ARG_MATCHER_FACTORY);
108
109 3
        $this->stmts = [];
110 3
        $this->references = [];
111 3
        unset($this->callback, $this->callbackCode, $this->capabilities);
112 3
    }
113
114
    public function onFinish(): void
115
    {
116
        $inputParam = $this
117
            ->php
118
            ->param(self::ARG_INPUT)
119
            ->setType(NodeValueListInterface::class)
120
            ->getNode();
121
        $valueListFetcherParam = $this
122
            ->php
123
            ->param(self::ARG_VALUE_LIST_FETCHER)
124
            ->setType(ValueListFetcherInterface::class)
125
            ->getNode();
126
        $evaluatorParam = $this
127
            ->php
128
            ->param(self::ARG_EVALUATOR)
129
            ->setType(EvaluatorInterface::class)
130
            ->getNode();
131
        $literalFactoryParam = $this
132
            ->php
133
            ->param(self::ARG_LITERAL_FACTORY)
134
            ->setType(LiteralFactoryInterface::class)
135
            ->getNode();
136
        $matcherFactoryParam = $this
137
            ->php
138
            ->param(self::ARG_MATCHER_FACTORY)
139
            ->setType(MatcherFactoryInterface::class)
140
            ->getNode();
141
        $stmts = array_map(
142
            function (PhpAstNode $stmt): PhpAstNode {
143
                return $stmt instanceof Expr ? new Expression($stmt) : $stmt;
144
            },
145
            $this->stmts
146
        );
147
148
        $closure = new Expr\Closure(
149
            [
150
                'stmts' => $stmts,
151
                'returnType' => ValueListInterface::class,
152
                'params' => [
153
                    $inputParam,
154
                    $valueListFetcherParam,
155
                    $evaluatorParam,
156
                    $literalFactoryParam,
157
                    $matcherFactoryParam
158
                ],
159
            ]
160
        );
161
        $return = new Return_($closure);
162
163
        $this->callbackCode = (new Standard())->prettyPrint([$return]);
164
    }
165
166 1
    public function onBeginProduction(QueryAstNode $node, PushInterface $stack): void
167
    {
168 1
        $stack->push(...array_reverse($node->getChildList()));
169 1
    }
170
171
    /**
172
     * @param QueryAstNode $node
173
     * @throws UniLexException
174
     */
175 3
    public function onFinishProduction(QueryAstNode $node): void
176
    {
177 3
        if ($this->hasReference($node)) {
178
            return;
179
        }
180 3
        switch ($node->getName()) {
181 3
            case AstNodeType::GET_INPUT:
182 2
                $this->setReference($node, $this->input);
183 2
                break;
184
185 3
            case AstNodeType::SET_OUTPUT:
186 3
                $this->capabilities = new Capabilities(
187 3
                    $node->getAttribute('is_definite'),
188 3
                    $node->getAttribute('is_addressable'),
189
                );
190 3
                $this->stmts[] = new Return_($this->getReference($node->getChild(0)));
191 2
                break;
192
193
            case AstNodeType::FETCH_CHILDREN:
194
                /** @see ValueListFetcherInterface::fetchChildren() */
195
                $this->addMethodCall(
196
                    $node,
197
                    $this->valueListFetcher,
198
                    'fetchChildren',
199
                    $this->getReference($node->getChild(0)),
200
                    $this->getReference($node->getChild(1)),
201
                );
202
                break;
203
204
            case AstNodeType::FETCH_CHILDREN_DEEP:
205
                /** @see ValueListFetcherInterface::fetchChildrenDeep() */
206
                $this->addMethodCall(
207
                    $node,
208
                    $this->valueListFetcher,
209
                    'fetchChildrenDeep',
210
                    $this->getReference($node->getChild(0)),
211
                    $this->getReference($node->getChild(1)),
212
                );
213
                break;
214
215
            case AstNodeType::MERGE:
216
                /** @see ValueListFetcherInterface::merge() */
217
                $this->addMethodCall(
218
                    $node,
219
                    $this->valueListFetcher,
220
                    'merge',
221
                    ...$this->getReferences(...$node->getChildList()),
222
                );
223
                break;
224
225
            case AstNodeType::FETCH_FILTER_CONTEXT:
226
                /** @see ValueListFetcherInterface::fetchFilterContext() */
227
                $this->addMethodCall(
228
                    $node,
229
                    $this->valueListFetcher,
230
                    'fetchFilterContext',
231
                    $this->getReference($node->getChild(0)),
232
                );
233
                break;
234
235
            case AstNodeType::SPLIT_FILTER_CONTEXT:
236
                /** @see ValueListFetcherInterface::splitFilterContext() */
237
                $this->addMethodCall(
238
                    $node,
239
                    $this->valueListFetcher,
240
                    'splitFilterContext',
241
                    $this->getReference($node->getChild(0)),
242
                );
243
                break;
244
245
            case AstNodeType::JOIN_FILTER_RESULTS:
246
                /** @see ValueListFetcherInterface::joinFilterResults() */
247
                $this->addMethodCall(
248
                    $node,
249
                    $this->valueListFetcher,
250
                    'joinFilterResults',
251
                    $this->getReference($node->getChild(0)),
252
                    $this->getReference($node->getChild(1)),
253
                );
254
                break;
255
256
            case AstNodeType::FILTER:
257
                /** @see ValueListFetcherInterface::fetchFilteredValues() */
258
                $this->addMethodCall(
259
                    $node,
260
                    $this->valueListFetcher,
261
                    'fetchFilteredValues',
262
                    $this->getReference($node->getChild(0)),
263
                    $this->getReference($node->getChild(1)),
264
                );
265
                break;
266
267
            case AstNodeType::EVALUATE:
268
                /** @see EvaluatorInterface::evaluate() */
269
                $this->addMethodCall(
270
                    $node,
271
                    $this->evaluator,
272
                    'evaluate',
273
                    $this->getReference($node->getChild(0)),
274
                    $this->getReference($node->getChild(1)),
275
                );
276
                break;
277
278
            case AstNodeType::EVALUATE_LOGICAL_OR:
279
                /** @see EvaluatorInterface::logicalOr() */
280
                $this->addMethodCall(
281
                    $node,
282
                    $this->evaluator,
283
                    'logicalOr',
284
                    $this->getReference($node->getChild(0)),
285
                    $this->getReference($node->getChild(1)),
286
                );
287
                break;
288
289
            case AstNodeType::EVALUATE_LOGICAL_AND:
290
                /** @see EvaluatorInterface::logicalAnd() */
291
                $this->addMethodCall(
292
                    $node,
293
                    $this->evaluator,
294
                    'logicalAnd',
295
                    $this->getReference($node->getChild(0)),
296
                    $this->getReference($node->getChild(1)),
297
                );
298
                break;
299
300
            case AstNodeType::EVALUATE_LOGICAL_NOT:
301
                /** @see EvaluatorInterface::logicalNot() */
302
                $this->addMethodCall(
303
                    $node,
304
                    $this->evaluator,
305
                    'logicalNot',
306
                    $this->getReference($node->getChild(0)),
307
                );
308
                break;
309
310
            case AstNodeType::CALCULATE_IS_EQUAL:
311
                /** @see EvaluatorInterface::isEqual() */
312
                $this->addMethodCall(
313
                    $node,
314
                    $this->evaluator,
315
                    'isEqual',
316
                    $this->getReference($node->getChild(0)),
317
                    $this->getReference($node->getChild(1)),
318
                );
319
                break;
320
321
            case AstNodeType::CALCULATE_IS_GREATER:
322
                /** @see EvaluatorInterface::isGreater() */
323
                $this->addMethodCall(
324
                    $node,
325
                    $this->evaluator,
326
                    'isGreater',
327
                    $this->getReference($node->getChild(0)),
328
                    $this->getReference($node->getChild(1)),
329
                );
330
                break;
331
332
            case AstNodeType::CALCULATE_IS_REGEXP:
333
                /** @see EvaluatorInterface::isRegExp() */
334
                $this->addMethodCall(
335
                    $node,
336
                    $this->evaluator,
337
                    'isRegExp',
338
                    $this->php->val($node->getAttribute('pattern')),
339
                    $this->getReference($node->getChild(0)),
340
                );
341
                break;
342
343
            case AstNodeType::MATCH_ANY_CHILD:
344
                /** @see MatcherFactoryInterface::matchAnyChild() */
345
                $this->addMethodCall(
346
                    $node,
347
                    $this->matcherFactory,
348
                    'matchAnyChild',
349
                );
350
                break;
351
352
            case AstNodeType::MATCH_PROPERTY_STRICTLY:
353
                /** @see MatcherFactoryInterface::matchPropertyStrictly() */
354
                $this->addMethodCall(
355
                    $node,
356
                    $this->matcherFactory,
357
                    'matchPropertyStrictly',
358
                    ...array_map(
359
                        [$this->php, 'val'],
360
                        $node->getAttribute('names')
361
                    ),
362
                );
363
                break;
364
365
            case AstNodeType::MATCH_ELEMENT_STRICTLY:
366
                /** @see MatcherFactoryInterface::matchElementStrictly() */
367
                $this->addMethodCall(
368
                    $node,
369
                    $this->matcherFactory,
370
                    'matchElementStrictly',
371
                    ...array_map(
372
                        [$this->php, 'val'],
373
                        $node->getAttribute('indexes')
374
                    ),
375
                );
376
                break;
377
378
            case AstNodeType::MATCH_ELEMENT_SLICE:
379
                /** @see MatcherFactoryInterface::matchElementSlice() */
380
                $this->addMethodCall(
381
                    $node,
382
                    $this->matcherFactory,
383
                    'matchElementSlice',
384
                    $this
385
                        ->php
386
                        ->val($node->getAttribute('hasStart') ? $node->getAttribute('start') : null),
387
                    $this
388
                        ->php
389
                        ->val($node->getAttribute('hasEnd') ? $node->getAttribute('end') : null),
390
                    $this
391
                        ->php
392
                        ->val($node->getAttribute('step')),
393
                );
394
                break;
395
396
            case AstNodeType::AGGREGATE:
397
                /** @see EvaluatorInterface::aggregate() */
398
                $this->addMethodCall(
399
                    $node,
400
                    $this->evaluator,
401
                    'aggregate',
402
                    $this->php->val($node->getAttribute('name')),
403
                    $this->getReference($node->getChild(0)),
404
                );
405
                break;
406
407
            case AstNodeType::CREATE_LITERAL_SCALAR:
408
                $attributes = $node->getAttributeList();
409
                $value = $attributes['value'] ?? null; // TODO: allow pass null in attribute
410
                /** @see LiteralFactoryInterface::createScalar() */
411
                $this->addMethodCall(
412
                    $node,
413
                    $this->literalFactory,
414
                    'createScalar',
415
                    $this->getReference($node->getChild(0)),
416
                    $this->php->val($value),
417
                );
418
                break;
419
420
            case AstNodeType::CREATE_LITERAL_ARRAY:
421
                /** @see LiteralFactoryInterface::createArray() */
422
                $this->addMethodCall(
423
                    $node,
424
                    $this->literalFactory,
425
                    'createArray',
426
                    $this->getReference($node->getChild(0)),
427
                    new Arg(
428
                        $this->getReference($node->getChild(1)),
429
                        false,
430
                        true,
431
                    ),
432
                );
433
                break;
434
435
            case AstNodeType::CREATE_ARRAY:
436
                // [ X:APPEND_TO_ARRAY ]
437
                $items = [];
438
                foreach ($node->getChildList() as $child) {
439
                    $items[] = $this->getReference($child);
440
                }
441
                $this->stmts[] = new Assign(
442
                    $this->createReference($node),
443
                    $this->php->val($items),
444
                );
445
                break;
446
447
            case AstNodeType::APPEND_TO_ARRAY:
448
                // [ 0:<value> ]
449
                $this->setReference(
450
                    $node,
451
                    $this->getReference($node->getChild(0)),
452
                );
453
                break;
454
        }
455 2
    }
456
457
    private function getVarName(QueryAstNode $node): string
458
    {
459
        return "var{$node->getId()}";
460
    }
461
462
    private function createReference(QueryAstNode $node): Expr
463
    {
464
        $reference = $this->php->var($this->getVarName($node));
465
        $this->setReference($node, $reference);
466
467
        return $reference;
468
    }
469
470 2
    private function setReference(QueryAstNode $node, Expr $expr): void
471
    {
472 2
        if (isset($this->references[$node->getId()])) {
473
            throw new Exception\ReferenceAlreadyExistsException($node->getId());
474
        }
475
476 2
        $this->references[$node->getId()] = $expr;
477 2
    }
478
479 3
    private function hasReference(QueryAstNode $node): bool
480
    {
481 3
        return isset($this->references[$node->getId()]);
482
    }
483
484 3
    private function getReference(QueryAstNode $node): Expr
485
    {
486 3
        if (!isset($this->references[$node->getId()])) {
487 1
            throw new Exception\ReferenceNotFoundException($node->getId());
488
        }
489
490 2
        return $this->references[$node->getId()];
491
    }
492
493
    /**
494
     * @param QueryAstNode ...$nodes
495
     * @return Expr[]
496
     */
497
    private function getReferences(QueryAstNode ...$nodes): array
498
    {
499
        return array_map([$this, 'getReference'], $nodes);
500
    }
501
502
    private function addMethodCall(QueryAstNode $node, Expr $object, string $method, PhpAstNode ...$args): void
503
    {
504
        $methodCall = $this
505
            ->php
506
            ->methodCall($object, $method, $args);
507
        $this->stmts[] = new Assign($this->createReference($node), $methodCall);
508
    }
509
}
510