Passed
Branch master (59a116)
by Dominik
04:40 queued 02:09
created

NodeGenerator::getLinesByCode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
crap 1
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 View Code Duplication
    private function createScalarNode($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...
78
    {
79 12
        if (is_string($value)) {
80 10
            $valueExpr = new String_($value);
81 5
        } elseif (is_int($value)) {
82 5
            $valueExpr = new LNumber($value);
83 1
        } elseif (is_float($value)) {
84 1
            $valueExpr = new DNumber($value);
85 1
        } elseif (is_bool($value)) {
86 1
            $valueExpr = new ConstFetch(new Name($value ? 'true' : 'false'));
87
        } else {
88 1
            $valueExpr = new ConstFetch(new Name('null'));
89
        }
90
91 12
        return new New_(new Name('ScalarNode'), [new Arg($valueExpr)]);
92
    }
93
94
    /**
95
     * @param array $data
96
     * @return Expr
97
     */
98 3
    private function appendChildrenToArrayNode(array $data)
99
    {
100 3
        $expr = $this->createArrayNode();
101
102 3
        foreach ($data as $key => $value) {
103 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...
104 1
                $nodeExpr = $this->createObjectNode();
105 3
            } elseif (is_array($value)) {
106 1
                $nodeExpr = $this->createArrayNode();
107
            } else {
108 2
                $nodeExpr = $this->createScalarNode($value);
109
            }
110
111 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...
112 1
                $nodeExpr = $this->appendChildrenToObjectNode($value);
113 3
            } elseif (is_array($value)) {
114 1
                $nodeExpr = $this->appendChildrenToArrayNode($value);
115
            }
116
117 3
            $expr = new MethodCall($expr, 'add', [new Arg($nodeExpr)]);
118
        }
119
120 3
        return $expr;
121
    }
122
123
    /**
124
     * @param \stdClass $data
125
     * @return Expr
126
     */
127 12
    private function appendChildrenToObjectNode(\stdClass $data)
128
    {
129 12
        $expr = $this->createObjectNode();
130
131 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...
132 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...
133 12
                $nodeExpr = $this->createObjectNode();
134 11
            } elseif (is_array($value)) {
135 2
                $nodeExpr = $this->createArrayNode();
136
            } else {
137 11
                $nodeExpr = $this->createScalarNode($value);
138
            }
139
140 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...
141 12
                $nodeExpr = $this->appendChildrenToObjectNode($value);
142 11
            } elseif (is_array($value)) {
143 2
                $nodeExpr = $this->appendChildrenToArrayNode($value);
144
            }
145
146 12
            $expr = new MethodCall($expr, 'add', [new Arg(new String_($key)), new Arg($nodeExpr)]);
147
        }
148
149 12
        return $expr;
150
    }
151
152
    /**
153
     * @param string $code
154
     * @return string
155
     */
156 13
    private function structureCode(string $code): string
157
    {
158 13
        $lines = $this->getLinesByCode($code);
159
160 13
        $position = 0;
161
162 13
        $structuredLines = [];
163
164 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...
165 13
            $lastStructuredLine = $structuredLines[count($structuredLines) - 1] ?? '';
166 13
            $this->structuredLine($line, $lastStructuredLine, $position, $structuredLines);
167
        }
168
169 13
        $structuredLines[count($structuredLines) - 1] .= ';';
170
171 13
        return implode("\n", $structuredLines);
172
    }
173
174
    /**
175
     * @param string $code
176
     * @return array
177
     */
178 13
    private function getLinesByCode(string $code): array
179
    {
180 13
        $codeWithLinebreaks = str_replace('->add', "\n->add", substr($code, 0, -1));
181
182 13
        return explode("\n", $codeWithLinebreaks);
183
    }
184
185
    /**
186
     * @param string $line
187
     * @param string $lastStructuredLine
188
     * @param int $position
189
     * @param array $structuredLines
190
     */
191 13
    private function structuredLine(string $line, string $lastStructuredLine, int &$position, array &$structuredLines)
192
    {
193 13
        if (0 === strpos($line, '->add') &&
194 13
            false === strpos($lastStructuredLine, ' )') &&
195 13
            false === strpos($lastStructuredLine, 'ScalarNode')) {
196 13
            $position++;
197
        }
198
199 13
        $lineLength = strlen($line);
200 13
        $braceCount = 0;
201
202 13
        while (')' === $line[--$lineLength]) {
203 13
            $braceCount++;
204
        }
205
206 13
        $prefix = str_pad('', $position * 4);
207
208 13
        if ($braceCount > 2) {
209 12
            $structuredLines[] = $prefix . substr($line, 0, - ($braceCount - 2));
210
        } else {
211 13
            $structuredLines[] = $prefix . $line;
212
        }
213
214 13
        while ($braceCount-- > 2) {
215 12
            $position--;
216 12
            $structuredLines[] = str_pad('', $position * 4) . ')';
217
        }
218 13
    }
219
}
220