Completed
Push — master ( 1b389e...c3c10e )
by Dominik
02:16
created

NodeGenerator::structuredLine()   C

Complexity

Conditions 11
Paths 16

Size

Total Lines 32
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 11

Importance

Changes 0
Metric Value
dl 0
loc 32
ccs 21
cts 21
cp 1
rs 5.2653
c 0
b 0
f 0
cc 11
eloc 21
nc 16
nop 4
crap 11

How to fix   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 Saxulum\ElasticSearchQueryBuilder\Generator;
6
7
use PhpParser\Node\Arg;
8
use PhpParser\Node\Expr;
9
use PhpParser\Node\Expr\Assign;
10
use PhpParser\Node\Expr\ConstFetch;
11
use PhpParser\Node\Expr\MethodCall;
12
use PhpParser\Node\Expr\New_;
13
use PhpParser\Node\Expr\Variable;
14
use PhpParser\Node\Name;
15
use PhpParser\Node\Scalar\DNumber;
16
use PhpParser\Node\Scalar\LNumber;
17
use PhpParser\Node\Scalar\String_;
18
use PhpParser\PrettyPrinter\Standard as PhpGenerator;
19
20
final class NodeGenerator
21
{
22
    /**
23
     * @var PhpGenerator
24
     */
25
    private $phpGenerator;
26
27
    /**
28
     * @param PhpGenerator $phpGenerator
29
     */
30 14
    public function __construct(PhpGenerator $phpGenerator)
31
    {
32 14
        $this->phpGenerator = $phpGenerator;
33 14
    }
34
35
    /**
36
     * @param $query
37
     * @return string
38
     */
39 14
    public function generateByJson($query): string
40
    {
41 14
        $data = json_decode($query, false);
42 14
        if (JSON_ERROR_NONE !== json_last_error()) {
43 1
            throw new \InvalidArgumentException(sprintf('Message: %s, query: %s', json_last_error_msg(), $query));
44
        }
45
46 13
        if ($data instanceof \stdClass) {
47 12
            $expr = $this->appendChildrenToObjectNode($data);
48
        } else {
49 1
            $expr = $this->appendChildrenToArrayNode($data);
50
        }
51
52 13
        $code = $this->phpGenerator->prettyPrint([new Assign(new Variable('node'), $expr)]);
53
54 13
        return $this->structureCode($code);
55
    }
56
57
    /**
58
     * @return Expr
59
     */
60 12
    private function createObjectNode(): Expr
61
    {
62 12
        return new New_(new Name('ObjectNode'));
63
    }
64
65
    /**
66
     * @return Expr
67
     */
68 3
    private function createArrayNode(): Expr
69
    {
70 3
        return new New_(new Name('ArrayNode'));
71
    }
72
73
    /**
74
     * @param string|float|int|bool|null $value
75
     * @return Expr
76
     */
77 12
    private function createScalarNode($value): Expr
78
    {
79 12
        if (is_int($value)) {
80 5
            return new New_(new Name('IntNode'), [new Arg(new LNumber($value))]);
81 10
        } elseif (is_float($value)) {
82 1
            return new New_(new Name('FloatNode'), [new Arg(new DNumber($value))]);
83 10
        } elseif (is_bool($value)) {
84 1
            return new New_(new Name('BoolNode'), [new Arg(new ConstFetch(new Name($value ? 'true' : 'false')))]);
85 10
        } elseif (null === $value) {
86 1
            return new New_(new Name('NullNode'));
87
        }
88
89 10
        return new New_(new Name('StringNode'), [new Arg(new String_($value))]);
90
    }
91
92
    /**
93
     * @param array $data
94
     * @return Expr
95
     */
96 3
    private function appendChildrenToArrayNode(array $data)
97
    {
98 3
        $expr = $this->createArrayNode();
99
100 3
        foreach ($data as $key => $value) {
101 3 View Code Duplication
            if ($value instanceof \stdClass) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
102 1
                $nodeExpr = $this->createObjectNode();
103 3
            } elseif (is_array($value)) {
104 1
                $nodeExpr = $this->createArrayNode();
105
            } else {
106 2
                $nodeExpr = $this->createScalarNode($value);
107
            }
108
109 3 View Code Duplication
            if ($value instanceof \stdClass) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
110 1
                $nodeExpr = $this->appendChildrenToObjectNode($value);
111 3
            } elseif (is_array($value)) {
112 1
                $nodeExpr = $this->appendChildrenToArrayNode($value);
113
            }
114
115 3
            $expr = new MethodCall($expr, 'add', [new Arg($nodeExpr)]);
116
        }
117
118 3
        return $expr;
119
    }
120
121
    /**
122
     * @param \stdClass $data
123
     * @return Expr
124
     */
125 12
    private function appendChildrenToObjectNode(\stdClass $data)
126
    {
127 12
        $expr = $this->createObjectNode();
128
129 12
        foreach ($data as $key => $value) {
0 ignored issues
show
Bug introduced by
The expression $data of type object<stdClass> is not traversable.
Loading history...
130 12 View Code Duplication
            if ($value instanceof \stdClass) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
131 12
                $nodeExpr = $this->createObjectNode();
132 11
            } elseif (is_array($value)) {
133 2
                $nodeExpr = $this->createArrayNode();
134
            } else {
135 11
                $nodeExpr = $this->createScalarNode($value);
136
            }
137
138 12 View Code Duplication
            if ($value instanceof \stdClass) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
139 12
                $nodeExpr = $this->appendChildrenToObjectNode($value);
140 11
            } elseif (is_array($value)) {
141 2
                $nodeExpr = $this->appendChildrenToArrayNode($value);
142
            }
143
144 12
            $expr = new MethodCall($expr, 'add', [new Arg(new String_($key)), new Arg($nodeExpr)]);
145
        }
146
147 12
        return $expr;
148
    }
149
150
    /**
151
     * @param string $code
152
     * @return string
153
     */
154 13
    private function structureCode(string $code): string
155
    {
156 13
        $lines = $this->getLinesByCode($code);
157
158 13
        $position = 0;
159
160 13
        $structuredLines = [];
161
162 13 View Code Duplication
        foreach ($lines as $i => $line) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
163 13
            $lastStructuredLine = $structuredLines[count($structuredLines) - 1] ?? '';
164 13
            $this->structuredLine($line, $lastStructuredLine, $position, $structuredLines);
165
        }
166
167 13
        $structuredLines[count($structuredLines) - 1] .= ';';
168
169 13
        return implode("\n", $structuredLines);
170
    }
171
172
    /**
173
     * @param string $code
174
     * @return array
175
     */
176 13
    private function getLinesByCode(string $code): array
177
    {
178 13
        $codeWithLinebreaks = str_replace('->add', "\n->add", substr($code, 0, -1));
179
180 13
        return explode("\n", $codeWithLinebreaks);
181
    }
182
183
    /**
184
     * @param string $line
185
     * @param string $lastStructuredLine
186
     * @param int $position
187
     * @param array $structuredLines
188
     */
189 13
    private function structuredLine(string $line, string $lastStructuredLine, int &$position, array &$structuredLines)
190
    {
191 13
        if (0 === strpos($line, '->add') &&
192 13
            false === strpos($lastStructuredLine, ' )') &&
193 13
            false === strpos($lastStructuredLine, 'BoolNode') &&
194 13
            false === strpos($lastStructuredLine, 'FloatNode') &&
195 13
            false === strpos($lastStructuredLine, 'IntNode') &&
196 13
            false === strpos($lastStructuredLine, 'NullNode') &&
197 13
            false === strpos($lastStructuredLine, 'StringNode')) {
198 13
            $position++;
199
        }
200
201 13
        $lineLength = strlen($line);
202 13
        $braceCount = 0;
203
204 13
        while (')' === $line[--$lineLength]) {
205 13
            $braceCount++;
206
        }
207
208 13
        $prefix = str_pad('', $position * 4);
209
210 13
        if ($braceCount > 2) {
211 12
            $structuredLines[] = $prefix . substr($line, 0, - ($braceCount - 2));
212
        } else {
213 13
            $structuredLines[] = $prefix . $line;
214
        }
215
216 13
        while ($braceCount-- > 2) {
217 12
            $position--;
218 12
            $structuredLines[] = str_pad('', $position * 4) . ')';
219
        }
220 13
    }
221
}
222