Passed
Push — master ( b68d1a...70ff0f )
by Dominik
04:42 queued 55s
created

NodeGenerator::createScalarNodeDefault()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 14
Code Lines 10

Duplication

Lines 14
Ratio 100 %

Code Coverage

Tests 10
CRAP Score 6

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 14
loc 14
ccs 10
cts 10
cp 1
rs 8.8571
cc 6
eloc 10
nc 5
nop 1
crap 6
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\StaticCall;
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
     * @var bool
29
     */
30
    private $useQueryBuilderFactory;
31
32
    /**
33
     * @param PhpGenerator $phpGenerator
34
     * @param bool         $useQueryBuilderFactory
35
     */
36 15
    public function __construct(PhpGenerator $phpGenerator, bool $useQueryBuilderFactory = false)
37
    {
38 15
        $this->phpGenerator = $phpGenerator;
39 15
        $this->useQueryBuilderFactory = $useQueryBuilderFactory;
40 15
    }
41
42
    /**
43
     * @param $query
44
     *
45
     * @return string
46
     */
47 15
    public function generateByJson($query): string
48
    {
49 15
        $data = json_decode($query, false);
50 15
        if (JSON_ERROR_NONE !== json_last_error()) {
51 1
            throw new \InvalidArgumentException(sprintf('Message: %s, query: %s', json_last_error_msg(), $query));
52
        }
53
54 14
        if ($data instanceof \stdClass) {
55 13
            $expr = $this->appendChildrenToObjectNode($data);
56
        } else {
57 1
            $expr = $this->appendChildrenToArrayNode($data);
58
        }
59
60 14
        $code = $this->phpGenerator->prettyPrint([new Assign(new Variable('node'), $expr)]);
61
62 14
        return $this->structureCode($code);
63
    }
64
65
    /**
66
     * @return Expr
67
     */
68 13 View Code Duplication
    private function createObjectNode(): Expr
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
69
    {
70 13
        if (!$this->useQueryBuilderFactory) {
71 12
            return new StaticCall(new Name('ObjectNode'), 'create');
72
        }
73
74 1
        return new MethodCall(new Variable('qb'), 'objectNode');
75
    }
76
77
    /**
78
     * @return Expr
79
     */
80 4 View Code Duplication
    private function createArrayNode(): Expr
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
81
    {
82 4
        if (!$this->useQueryBuilderFactory) {
83 3
            return new StaticCall(new Name('ArrayNode'), 'create');
84
        }
85
86 1
        return new MethodCall(new Variable('qb'), 'arrayNode');
87
    }
88
89
    /**
90
     * @param string|float|int|bool|null $value
91
     *
92
     * @return Expr
93
     */
94 13
    private function createScalarNode($value): Expr
95
    {
96 13
        if (!$this->useQueryBuilderFactory) {
97 12
            return $this->createScalarNodeDefault($value);
98
        }
99
100 1
        return $this->createScalarNodeQueryBuilderFactory($value);
101
    }
102
103
    /**
104
     * @param string|float|int|bool|null $value
105
     *
106
     * @return Expr
107
     */
108 12 View Code Duplication
    private function createScalarNodeDefault($value): Expr
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
109
    {
110 12
        if (is_int($value)) {
111 5
            return new StaticCall(new Name('IntNode'), 'create', [new Arg(new LNumber($value))]);
112 10
        } elseif (is_float($value)) {
113 1
            return new StaticCall(new Name('FloatNode'), 'create', [new Arg(new DNumber($value))]);
114 10
        } elseif (is_bool($value)) {
115 1
            return new StaticCall(new Name('BoolNode'), 'create', [new Arg(new ConstFetch(new Name($value ? 'true' : 'false')))]);
116 10
        } elseif (null === $value) {
117 1
            return new StaticCall(new Name('NullNode'), 'create');
118
        }
119
120 10
        return new StaticCall(new Name('StringNode'), 'create', [new Arg(new String_($value))]);
121
    }
122
123
    /**
124
     * @param string|float|int|bool|null $value
125
     *
126
     * @return Expr
127
     */
128 1 View Code Duplication
    private function createScalarNodeQueryBuilderFactory($value): Expr
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
129
    {
130 1
        if (is_int($value)) {
131 1
            return new MethodCall(new Variable('qb'), 'intNode', [new Arg(new LNumber($value))]);
132 1
        } elseif (is_float($value)) {
133 1
            return new MethodCall(new Variable('qb'), 'floatNode', [new Arg(new DNumber($value))]);
134 1
        } elseif (is_bool($value)) {
135 1
            return new MethodCall(new Variable('qb'), 'boolNode', [new Arg(new ConstFetch(new Name($value ? 'true' : 'false')))]);
136 1
        } elseif (null === $value) {
137 1
            return new MethodCall(new Variable('qb'), 'nullNode');
138
        }
139
140 1
        return new MethodCall(new Variable('qb'), 'stringNode', [new Arg(new String_($value))]);
141
    }
142
143
    /**
144
     * @param array $data
145
     *
146
     * @return Expr
147
     */
148 4
    private function appendChildrenToArrayNode(array $data)
149
    {
150 4
        $expr = $this->createArrayNode();
151
152 4
        foreach ($data as $key => $value) {
153 4 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...
154 2
                $nodeExpr = $this->createObjectNode();
155 4
            } elseif (is_array($value)) {
156 2
                $nodeExpr = $this->createArrayNode();
157
            } else {
158 2
                $nodeExpr = $this->createScalarNode($value);
159
            }
160
161 4 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...
162 2
                $nodeExpr = $this->appendChildrenToObjectNode($value);
163 4
            } elseif (is_array($value)) {
164 2
                $nodeExpr = $this->appendChildrenToArrayNode($value);
165
            }
166
167 4
            $expr = new MethodCall($expr, 'add', [new Arg($nodeExpr)]);
168
        }
169
170 4
        return $expr;
171
    }
172
173
    /**
174
     * @param \stdClass $data
175
     *
176
     * @return Expr
177
     */
178 13
    private function appendChildrenToObjectNode(\stdClass $data)
179
    {
180 13
        $expr = $this->createObjectNode();
181
182 13
        foreach ($data as $key => $value) {
0 ignored issues
show
Bug introduced by
The expression $data of type object<stdClass> is not traversable.
Loading history...
183 13 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...
184 13
                $nodeExpr = $this->createObjectNode();
185 12
            } elseif (is_array($value)) {
186 3
                $nodeExpr = $this->createArrayNode();
187
            } else {
188 12
                $nodeExpr = $this->createScalarNode($value);
189
            }
190
191 13 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...
192 13
                $nodeExpr = $this->appendChildrenToObjectNode($value);
193 12
            } elseif (is_array($value)) {
194 3
                $nodeExpr = $this->appendChildrenToArrayNode($value);
195
            }
196
197 13
            $expr = new MethodCall($expr, 'add', [new Arg(new String_($key)), new Arg($nodeExpr)]);
198
        }
199
200 13
        return $expr;
201
    }
202
203
    /**
204
     * @param string $code
205
     *
206
     * @return string
207
     */
208 14
    private function structureCode(string $code): string
209
    {
210 14
        $lines = $this->getLinesByCode($code);
211
212 14
        $position = 0;
213
214 14
        $structuredLines = [];
215
216 14 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...
217 14
            $lastStructuredLine = $structuredLines[count($structuredLines) - 1] ?? '';
218 14
            $this->structuredLine($line, $lastStructuredLine, $position, $structuredLines);
219
        }
220
221 14
        $structuredLines[count($structuredLines) - 1] .= ';';
222
223 14
        return implode("\n", $structuredLines);
224
    }
225
226
    /**
227
     * @param string $code
228
     *
229
     * @return array
230
     */
231 14
    private function getLinesByCode(string $code): array
232
    {
233 14
        $codeWithLinebreaks = str_replace('->add', "\n->add", substr($code, 0, -1));
234
235 14
        return explode("\n", $codeWithLinebreaks);
236
    }
237
238
    /**
239
     * @param string $line
240
     * @param string $lastStructuredLine
241
     * @param int    $position
242
     * @param array  $structuredLines
243
     */
244 14
    private function structuredLine(string $line, string $lastStructuredLine, int &$position, array &$structuredLines)
245
    {
246 14
        if (0 === strpos($line, '->add') &&
247 14
            false === strpos($lastStructuredLine, ' )') &&
248 14
            false === strpos($lastStructuredLine, 'oolNode') &&
249 14
            false === strpos($lastStructuredLine, 'loatNode') &&
250 14
            false === strpos($lastStructuredLine, 'ntNode') &&
251 14
            false === strpos($lastStructuredLine, 'ullNode') &&
252 14
            false === strpos($lastStructuredLine, 'tringNode')) {
253 14
            ++$position;
254
        }
255
256 14
        $lineLength = strlen($line);
257 14
        $braceCount = 0;
258
259 14
        while (')' === $line[--$lineLength]) {
260 14
            ++$braceCount;
261
        }
262
263 14
        $prefix = str_pad('', $position * 4);
264
265 14
        if ($braceCount > 2) {
266 13
            $structuredLines[] = $prefix.substr($line, 0, -($braceCount - 2));
267
        } else {
268 14
            $structuredLines[] = $prefix.$line;
269
        }
270
271 14
        while ($braceCount-- > 2) {
272 13
            --$position;
273 13
            $structuredLines[] = str_pad('', $position * 4).')';
274
        }
275 14
    }
276
}
277