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