Completed
Push — master ( 56ea51...5cce8c )
by Edward
06:13
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 3
    }
107
108
    public function onFinish(): void
109
    {
110
        $inputParam = $this
111
            ->php
112
            ->param(self::ARG_INPUT)
113
            ->setType(NodeValueListInterface::class)
114
            ->getNode();
115
        $valueListFetcherParam = $this
116
            ->php
117
            ->param(self::ARG_VALUE_LIST_FETCHER)
118
            ->setType(ValueListFetcherInterface::class)
119
            ->getNode();
120
        $evaluatorParam = $this
121
            ->php
122
            ->param(self::ARG_EVALUATOR)
123
            ->setType(EvaluatorInterface::class)
124
            ->getNode();
125
        $literalFactoryParam = $this
126
            ->php
127
            ->param(self::ARG_LITERAL_FACTORY)
128
            ->setType(LiteralFactoryInterface::class)
129
            ->getNode();
130
        $matcherFactoryParam = $this
131
            ->php
132
            ->param(self::ARG_MATCHER_FACTORY)
133
            ->setType(MatcherFactoryInterface::class)
134
            ->getNode();
135
        $stmts = array_map(
136
            function (PhpAstNode $stmt): PhpAstNode {
137
                return $stmt instanceof Expr ? new Expression($stmt): $stmt;
138
            },
139
            $this->stmts
140
        );
141
142
        $closure = new Expr\Closure(
143
            [
144
                'stmts' => $stmts,
145
                'returnType' => ValueListInterface::class,
146
                'params' => [
147
                    $inputParam,
148
                    $valueListFetcherParam,
149
                    $evaluatorParam,
150
                    $literalFactoryParam,
151
                    $matcherFactoryParam
152
                ],
153
            ]
154
        );
155
        $return = new Return_($closure);
156
157
        $this->callbackCode = (new Standard)->prettyPrint([$return]);
158
        $this->callback = eval($this->callbackCode);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
159
    }
160
161 1
    public function onBeginProduction(QueryAstNode $node, PushInterface $stack): void
162
    {
163 1
        $stack->push(...array_reverse($node->getChildList()));
164 1
    }
165
166
    /**
167
     * @param QueryAstNode $node
168
     * @throws UniLexException
169
     */
170 3
    public function onFinishProduction(QueryAstNode $node): void
171
    {
172 3
        if ($this->hasReference($node)) {
173
            return;
174
        }
175 3
        switch ($node->getName()) {
176
            case AstNodeType::GET_INPUT:
177 2
                $this->setReference($node, $this->input);
178 2
                break;
179
180
            case AstNodeType::SET_OUTPUT:
181 3
                $this->capabilities = new Capabilities(
182 3
                    $node->getAttribute('is_definite'),
183 3
                    $node->getAttribute('is_path'),
184
                );
185 3
                $this->stmts[] = new Return_($this->getReference($node->getChild(0)));
186 2
                break;
187
188
            case AstNodeType::FETCH_CHILDREN:
189
                /** @see ValueListFetcherInterface::fetchChildren() */
190
                $this->addMethodCall(
191
                    $node,
192
                    $this->valueListFetcher,
193
                    'fetchChildren',
194
                    $this->getReference($node->getChild(0)),
195
                    $this->getReference($node->getChild(1)),
196
                );
197
                break;
198
199
            case AstNodeType::FETCH_CHILDREN_DEEP:
200
                /** @see ValueListFetcherInterface::fetchChildrenDeep() */
201
                $this->addMethodCall(
202
                    $node,
203
                    $this->valueListFetcher,
204
                    'fetchChildrenDeep',
205
                    $this->getReference($node->getChild(0)),
206
                    $this->getReference($node->getChild(1)),
207
                );
208
                break;
209
210
            case AstNodeType::FETCH_FILTER_CONTEXT:
211
                /** @see ValueListFetcherInterface::fetchFilterContext() */
212
                $this->addMethodCall(
213
                    $node,
214
                    $this->valueListFetcher,
215
                    'fetchFilterContext',
216
                    $this->getReference($node->getChild(0)),
217
                );
218
                break;
219
220
            case AstNodeType::SPLIT_FILTER_CONTEXT:
221
                /** @see ValueListFetcherInterface::splitFilterContext() */
222
                $this->addMethodCall(
223
                    $node,
224
                    $this->valueListFetcher,
225
                    'splitFilterContext',
226
                    $this->getReference($node->getChild(0)),
227
                );
228
                break;
229
230
            case AstNodeType::JOIN_FILTER_RESULTS:
231
                /** @see ValueListFetcherInterface::joinFilterResults() */
232
                $this->addMethodCall(
233
                    $node,
234
                    $this->valueListFetcher,
235
                    'joinFilterResults',
236
                    $this->getReference($node->getChild(0)),
237
                    $this->getReference($node->getChild(1)),
238
                );
239
                break;
240
241
            case AstNodeType::FILTER:
242
                /** @see ValueListFetcherInterface::fetchFilteredValues() */
243
                $this->addMethodCall(
244
                    $node,
245
                    $this->valueListFetcher,
246
                    'fetchFilteredValues',
247
                    $this->getReference($node->getChild(0)),
248
                    $this->getReference($node->getChild(1)),
249
                );
250
                break;
251
252
            case AstNodeType::EVALUATE:
253
                /** @see EvaluatorInterface::evaluate() */
254
                $this->addMethodCall(
255
                    $node,
256
                    $this->evaluator,
257
                    'evaluate',
258
                    $this->getReference($node->getChild(0)),
259
                    $this->getReference($node->getChild(1)),
260
                );
261
                break;
262
263
            case AstNodeType::EVALUATE_LOGICAL_OR:
264
                /** @see EvaluatorInterface::logicalOr() */
265
                $this->addMethodCall(
266
                    $node,
267
                    $this->evaluator,
268
                    'logicalOr',
269
                    $this->getReference($node->getChild(0)),
270
                    $this->getReference($node->getChild(1)),
271
                );
272
                break;
273
274
            case AstNodeType::EVALUATE_LOGICAL_AND:
275
                /** @see EvaluatorInterface::logicalAnd() */
276
                $this->addMethodCall(
277
                    $node,
278
                    $this->evaluator,
279
                    'logicalAnd',
280
                    $this->getReference($node->getChild(0)),
281
                    $this->getReference($node->getChild(1)),
282
                );
283
                break;
284
285
            case AstNodeType::EVALUATE_LOGICAL_NOT:
286
                /** @see EvaluatorInterface::logicalNot() */
287
                $this->addMethodCall(
288
                    $node,
289
                    $this->evaluator,
290
                    'logicalNot',
291
                    $this->getReference($node->getChild(0)),
292
                );
293
                break;
294
295
            case AstNodeType::CALCULATE_IS_EQUAL:
296
                /** @see EvaluatorInterface::isEqual() */
297
                $this->addMethodCall(
298
                    $node,
299
                    $this->evaluator,
300
                    'isEqual',
301
                    $this->getReference($node->getChild(0)),
302
                    $this->getReference($node->getChild(1)),
303
                );
304
                break;
305
306
            case AstNodeType::CALCULATE_IS_GREATER:
307
                /** @see EvaluatorInterface::isGreater() */
308
                $this->addMethodCall(
309
                    $node,
310
                    $this->evaluator,
311
                    'isGreater',
312
                    $this->getReference($node->getChild(0)),
313
                    $this->getReference($node->getChild(1)),
314
                );
315
                break;
316
317
            case AstNodeType::CALCULATE_IS_REGEXP:
318
                /** @see EvaluatorInterface::isRegExp() */
319
                $this->addMethodCall(
320
                    $node,
321
                    $this->evaluator,
322
                    'isRegExp',
323
                    $this->php->val($node->getAttribute('pattern')),
324
                    $this->getReference($node->getChild(0)),
325
                );
326
                break;
327
328
            case AstNodeType::MATCH_ANY_CHILD:
329
                /** @see MatcherFactoryInterface::matchAnyChild() */
330
                $this->addMethodCall(
331
                    $node,
332
                    $this->matcherFactory,
333
                    'matchAnyChild',
334
                );
335
                break;
336
337
            case AstNodeType::MATCH_PROPERTY_STRICTLY:
338
                /** @see MatcherFactoryInterface::matchPropertyStrictly() */
339
                $this->addMethodCall(
340
                    $node,
341
                    $this->matcherFactory,
342
                    'matchPropertyStrictly',
343
                    ...array_map(
344
                        [$this->php, 'val'],
345
                        $node->getAttribute('names')
346
                    ),
347
                );
348
                break;
349
350
            case AstNodeType::MATCH_ELEMENT_STRICTLY:
351
                /** @see MatcherFactoryInterface::matchElementStrictly() */
352
                $this->addMethodCall(
353
                    $node,
354
                    $this->matcherFactory,
355
                    'matchElementStrictly',
356
                    ...array_map(
357
                        [$this->php, 'val'],
358
                        $node->getAttribute('indexes')
359
                    ),
360
                );
361
                break;
362
363
            case AstNodeType::MATCH_ELEMENT_SLICE:
364
                /** @see MatcherFactoryInterface::matchElementSlice() */
365
                $this->addMethodCall(
366
                    $node,
367
                    $this->matcherFactory,
368
                    'matchElementSlice',
369
                    $this
370
                        ->php
371
                        ->val($node->getAttribute('hasStart') ? $node->getAttribute('start') : null),
372
                    $this
373
                        ->php
374
                        ->val($node->getAttribute('hasEnd') ? $node->getAttribute('end') : null),
375
                    $this
376
                        ->php
377
                        ->val($node->getAttribute('step')),
378
                );
379
                break;
380
381
            case AstNodeType::AGGREGATE:
382
                /** @see EvaluatorInterface::aggregate() */
383
                $this->addMethodCall(
384
                    $node,
385
                    $this->evaluator,
386
                    'aggregate',
387
                    $this->php->val($node->getAttribute('name')),
388
                    $this->getReference($node->getChild(0)),
389
                );
390
                break;
391
392
            case AstNodeType::CREATE_LITERAL_SCALAR:
393
                $attributes = $node->getAttributeList();
394
                $value = $attributes['value'] ?? null; // TODO: allow pass null in attribute
395
                /** @see LiteralFactoryInterface::createScalar() */
396
                $this->addMethodCall(
397
                    $node,
398
                    $this->literalFactory,
399
                    'createScalar',
400
                    $this->getReference($node->getChild(0)),
401
                    $this->php->val($value),
402
                );
403
                break;
404
405
            case AstNodeType::CREATE_LITERAL_ARRAY:
406
                /** @see LiteralFactoryInterface::createArray() */
407
                $this->addMethodCall(
408
                    $node,
409
                    $this->literalFactory,
410
                    'createArray',
411
                    $this->getReference($node->getChild(0)),
412
                    new Arg(
413
                        $this->getReference($node->getChild(1)),
414
                        false,
415
                        true,
416
                    ),
417
                );
418
                break;
419
420
            case AstNodeType::CREATE_ARRAY:
421
                // [ X:APPEND_TO_ARRAY ]
422
                $items = [];
423
                foreach ($node->getChildList() as $child) {
424
                    $items[] = $this->getReference($child);
425
                }
426
                $this->stmts[] = new Assign(
427
                    $this->createReference($node),
428
                    $this->php->val($items),
429
                );
430
                break;
431
432
            case AstNodeType::APPEND_TO_ARRAY:
433
                // [ 0:<value> ]
434
                $this->setReference(
435
                    $node,
436
                    $this->getReference($node->getChild(0)),
437
                );
438
                break;
439
        }
440 2
    }
441
442
    private function getVarName(QueryAstNode $node): string
443
    {
444
        return "var{$node->getId()}";
445
    }
446
447
    private function createReference(QueryAstNode $node): Expr
448
    {
449
        $reference = $this->php->var($this->getVarName($node));
450
        $this->setReference($node, $reference);
451
452
        return $reference;
453
    }
454
455 2
    private function setReference(QueryAstNode $node, Expr $expr): void
456
    {
457 2
        if (isset($this->references[$node->getId()])) {
458
            throw new Exception\ReferenceAlreadyExistsException($node->getId());
459
        }
460
461 2
        $this->references[$node->getId()] = $expr;
462 2
    }
463
464 3
    private function hasReference(QueryAstNode $node): bool
465
    {
466 3
        return isset($this->references[$node->getId()]);
467
    }
468
469 3
    private function getReference(QueryAstNode $node): Expr
470
    {
471 3
        if (!isset($this->references[$node->getId()])) {
472 1
            throw new Exception\ReferenceNotFoundException($node->getId());
473
        }
474
475 2
        return $this->references[$node->getId()];
476
    }
477
478
    private function addMethodCall(QueryAstNode $node, Expr $object, string $method, PhpAstNode ...$args): void
479
    {
480
        $methodCall = $this
481
            ->php
482
            ->methodCall($object, $method, $args);
483
        $this->stmts[] = new Assign($this->createReference($node), $methodCall);
484
    }
485
}
486