NodeGenerator::structureCode()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 17

Duplication

Lines 4
Ratio 23.53 %

Code Coverage

Tests 9
CRAP Score 2

Importance

Changes 0
Metric Value
dl 4
loc 17
ccs 9
cts 9
cp 1
rs 9.7
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
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
41 15
        if ($useQueryBuilderFactory) {
42 1
            @trigger_error('Argument $useQueryBuilderFactory will be removed', E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

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