Completed
Push — master ( 345204...d371ed )
by Edward
04:50
created

CallbackBuilder::onFinishProduction()   F

Complexity

Conditions 29
Paths 27

Size

Total Lines 269
Code Lines 194

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 728.6363

Importance

Changes 0
Metric Value
cc 29
eloc 194
c 0
b 0
f 0
nc 27
nop 1
dl 0
loc 269
ccs 10
cts 168
cp 0.0595
crap 728.6363
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::FETCH_FILTER_CONTEXT:
214
                /** @see ValueListFetcherInterface::fetchFilterContext() */
215
                $this->addMethodCall(
216
                    $node,
217
                    $this->valueListFetcher,
218
                    'fetchFilterContext',
219
                    $this->getReference($node->getChild(0)),
220
                );
221
                break;
222
223
            case AstNodeType::SPLIT_FILTER_CONTEXT:
224
                /** @see ValueListFetcherInterface::splitFilterContext() */
225
                $this->addMethodCall(
226
                    $node,
227
                    $this->valueListFetcher,
228
                    'splitFilterContext',
229
                    $this->getReference($node->getChild(0)),
230
                );
231
                break;
232
233
            case AstNodeType::JOIN_FILTER_RESULTS:
234
                /** @see ValueListFetcherInterface::joinFilterResults() */
235
                $this->addMethodCall(
236
                    $node,
237
                    $this->valueListFetcher,
238
                    'joinFilterResults',
239
                    $this->getReference($node->getChild(0)),
240
                    $this->getReference($node->getChild(1)),
241
                );
242
                break;
243
244
            case AstNodeType::FILTER:
245
                /** @see ValueListFetcherInterface::fetchFilteredValues() */
246
                $this->addMethodCall(
247
                    $node,
248
                    $this->valueListFetcher,
249
                    'fetchFilteredValues',
250
                    $this->getReference($node->getChild(0)),
251
                    $this->getReference($node->getChild(1)),
252
                );
253
                break;
254
255
            case AstNodeType::EVALUATE:
256
                /** @see EvaluatorInterface::evaluate() */
257
                $this->addMethodCall(
258
                    $node,
259
                    $this->evaluator,
260
                    'evaluate',
261
                    $this->getReference($node->getChild(0)),
262
                    $this->getReference($node->getChild(1)),
263
                );
264
                break;
265
266
            case AstNodeType::EVALUATE_LOGICAL_OR:
267
                /** @see EvaluatorInterface::logicalOr() */
268
                $this->addMethodCall(
269
                    $node,
270
                    $this->evaluator,
271
                    'logicalOr',
272
                    $this->getReference($node->getChild(0)),
273
                    $this->getReference($node->getChild(1)),
274
                );
275
                break;
276
277
            case AstNodeType::EVALUATE_LOGICAL_AND:
278
                /** @see EvaluatorInterface::logicalAnd() */
279
                $this->addMethodCall(
280
                    $node,
281
                    $this->evaluator,
282
                    'logicalAnd',
283
                    $this->getReference($node->getChild(0)),
284
                    $this->getReference($node->getChild(1)),
285
                );
286
                break;
287
288
            case AstNodeType::EVALUATE_LOGICAL_NOT:
289
                /** @see EvaluatorInterface::logicalNot() */
290
                $this->addMethodCall(
291
                    $node,
292
                    $this->evaluator,
293
                    'logicalNot',
294
                    $this->getReference($node->getChild(0)),
295
                );
296
                break;
297
298
            case AstNodeType::CALCULATE_IS_EQUAL:
299
                /** @see EvaluatorInterface::isEqual() */
300
                $this->addMethodCall(
301
                    $node,
302
                    $this->evaluator,
303
                    'isEqual',
304
                    $this->getReference($node->getChild(0)),
305
                    $this->getReference($node->getChild(1)),
306
                );
307
                break;
308
309
            case AstNodeType::CALCULATE_IS_GREATER:
310
                /** @see EvaluatorInterface::isGreater() */
311
                $this->addMethodCall(
312
                    $node,
313
                    $this->evaluator,
314
                    'isGreater',
315
                    $this->getReference($node->getChild(0)),
316
                    $this->getReference($node->getChild(1)),
317
                );
318
                break;
319
320
            case AstNodeType::CALCULATE_IS_REGEXP:
321
                /** @see EvaluatorInterface::isRegExp() */
322
                $this->addMethodCall(
323
                    $node,
324
                    $this->evaluator,
325
                    'isRegExp',
326
                    $this->php->val($node->getAttribute('pattern')),
327
                    $this->getReference($node->getChild(0)),
328
                );
329
                break;
330
331
            case AstNodeType::MATCH_ANY_CHILD:
332
                /** @see MatcherFactoryInterface::matchAnyChild() */
333
                $this->addMethodCall(
334
                    $node,
335
                    $this->matcherFactory,
336
                    'matchAnyChild',
337
                );
338
                break;
339
340
            case AstNodeType::MATCH_PROPERTY_STRICTLY:
341
                /** @see MatcherFactoryInterface::matchPropertyStrictly() */
342
                $this->addMethodCall(
343
                    $node,
344
                    $this->matcherFactory,
345
                    'matchPropertyStrictly',
346
                    ...array_map(
347
                        [$this->php, 'val'],
348
                        $node->getAttribute('names')
349
                    ),
350
                );
351
                break;
352
353
            case AstNodeType::MATCH_ELEMENT_STRICTLY:
354
                /** @see MatcherFactoryInterface::matchElementStrictly() */
355
                $this->addMethodCall(
356
                    $node,
357
                    $this->matcherFactory,
358
                    'matchElementStrictly',
359
                    ...array_map(
360
                        [$this->php, 'val'],
361
                        $node->getAttribute('indexes')
362
                    ),
363
                );
364
                break;
365
366
            case AstNodeType::MATCH_ELEMENT_SLICE:
367
                /** @see MatcherFactoryInterface::matchElementSlice() */
368
                $this->addMethodCall(
369
                    $node,
370
                    $this->matcherFactory,
371
                    'matchElementSlice',
372
                    $this
373
                        ->php
374
                        ->val($node->getAttribute('hasStart') ? $node->getAttribute('start') : null),
375
                    $this
376
                        ->php
377
                        ->val($node->getAttribute('hasEnd') ? $node->getAttribute('end') : null),
378
                    $this
379
                        ->php
380
                        ->val($node->getAttribute('step')),
381
                );
382
                break;
383
384
            case AstNodeType::AGGREGATE:
385
                /** @see EvaluatorInterface::aggregate() */
386
                $this->addMethodCall(
387
                    $node,
388
                    $this->evaluator,
389
                    'aggregate',
390
                    $this->php->val($node->getAttribute('name')),
391
                    $this->getReference($node->getChild(0)),
392
                );
393
                break;
394
395
            case AstNodeType::CREATE_LITERAL_SCALAR:
396
                $attributes = $node->getAttributeList();
397
                $value = $attributes['value'] ?? null; // TODO: allow pass null in attribute
398
                /** @see LiteralFactoryInterface::createScalar() */
399
                $this->addMethodCall(
400
                    $node,
401
                    $this->literalFactory,
402
                    'createScalar',
403
                    $this->getReference($node->getChild(0)),
404
                    $this->php->val($value),
405
                );
406
                break;
407
408
            case AstNodeType::CREATE_LITERAL_ARRAY:
409
                /** @see LiteralFactoryInterface::createArray() */
410
                $this->addMethodCall(
411
                    $node,
412
                    $this->literalFactory,
413
                    'createArray',
414
                    $this->getReference($node->getChild(0)),
415
                    new Arg(
416
                        $this->getReference($node->getChild(1)),
417
                        false,
418
                        true,
419
                    ),
420
                );
421
                break;
422
423
            case AstNodeType::CREATE_ARRAY:
424
                // [ X:APPEND_TO_ARRAY ]
425
                $items = [];
426
                foreach ($node->getChildList() as $child) {
427
                    $items[] = $this->getReference($child);
428
                }
429
                $this->stmts[] = new Assign(
430
                    $this->createReference($node),
431
                    $this->php->val($items),
432
                );
433
                break;
434
435
            case AstNodeType::APPEND_TO_ARRAY:
436
                // [ 0:<value> ]
437
                $this->setReference(
438
                    $node,
439
                    $this->getReference($node->getChild(0)),
440
                );
441
                break;
442
        }
443 2
    }
444
445
    private function getVarName(QueryAstNode $node): string
446
    {
447
        return "var{$node->getId()}";
448
    }
449
450
    private function createReference(QueryAstNode $node): Expr
451
    {
452
        $reference = $this->php->var($this->getVarName($node));
453
        $this->setReference($node, $reference);
454
455
        return $reference;
456
    }
457
458 2
    private function setReference(QueryAstNode $node, Expr $expr): void
459
    {
460 2
        if (isset($this->references[$node->getId()])) {
461
            throw new Exception\ReferenceAlreadyExistsException($node->getId());
462
        }
463
464 2
        $this->references[$node->getId()] = $expr;
465 2
    }
466
467 3
    private function hasReference(QueryAstNode $node): bool
468
    {
469 3
        return isset($this->references[$node->getId()]);
470
    }
471
472 3
    private function getReference(QueryAstNode $node): Expr
473
    {
474 3
        if (!isset($this->references[$node->getId()])) {
475 1
            throw new Exception\ReferenceNotFoundException($node->getId());
476
        }
477
478 2
        return $this->references[$node->getId()];
479
    }
480
481
    private function addMethodCall(QueryAstNode $node, Expr $object, string $method, PhpAstNode ...$args): void
482
    {
483
        $methodCall = $this
484
            ->php
485
            ->methodCall($object, $method, $args);
486
        $this->stmts[] = new Assign($this->createReference($node), $methodCall);
487
    }
488
}
489