Completed
Push — master ( e278ac...454097 )
by Edward
05:17
created

CallbackBuilder::onFinishProduction()   F

Complexity

Conditions 30
Paths 28

Size

Total Lines 279
Code Lines 201

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 783.5057

Importance

Changes 0
Metric Value
cc 30
eloc 201
c 0
b 0
f 0
nc 28
nop 1
dl 0
loc 279
ccs 10
cts 174
cp 0.0575
crap 783.5057
rs 3.3333

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