Completed
Branch master (f05bc6)
by Dominik
05:10 queued 02:31
created

QueryBuilderGenerator::structureCode()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 8
cts 8
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 8
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\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 QueryBuilderGenerator
21
{
22
    /**
23
     * @var PhpGenerator
24
     */
25
    private $phpGenerator;
26
27
    /**
28
     * @param PhpGenerator $phpGenerator
29
     */
30 13
    public function __construct(PhpGenerator $phpGenerator)
31
    {
32 13
        $this->phpGenerator = $phpGenerator;
33 13
    }
34
35
    /**
36
     * @param $query
37
     * @return string
38
     */
39 13
    public function generateByJson($query): string
40
    {
41 13
        $data = json_decode($query, false);
42 13
        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 12
        $queryBuilder = new Variable('queryBuilder');
47
48 12
        $stmts = [];
49
50 12
        $stmts[] = $this->createQueryBuilderNode();
51 12
        $stmts[] = $this->appendChildrenToObjectNode($queryBuilder, $queryBuilder, $data);
52
53 12
        return $this->structureCode($this->phpGenerator->prettyPrint($stmts));
54
    }
55
56
    /**
57
     * @return Expr
58
     */
59 12
    private function createQueryBuilderNode(): Expr
60
    {
61 12
        return new Assign(new Variable('queryBuilder'), new New_(new Name('QueryBuilder')));
62
    }
63
64
    /**
65
     * @param Expr $expr
66
     * @return Expr
67
     */
68 12
    private function createObjectNode(Expr $expr): Expr
69
    {
70 12
        return new MethodCall($expr, 'objectNode');
71
    }
72
73
    /**
74
     * @param Expr $expr
75
     * @return Expr
76
     */
77 2
    private function createArrayNode(Expr $expr): Expr
78
    {
79 2
        return new MethodCall($expr, 'arrayNode');
80
    }
81
82
    /**
83
     * @param Expr $expr
84
     * @param string|float|int|bool|null $value
85
     * @return Expr
86
     */
87 11 View Code Duplication
    private function createScalarNode(Expr $expr, $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...
88
    {
89 11
        if (is_string($value)) {
90 10
            $valueExpr = new String_($value);
91 4
        } elseif (is_int($value)) {
92 4
            $valueExpr = new LNumber($value);
93 1
        } elseif (is_float($value)) {
94 1
            $valueExpr = new DNumber($value);
95 1
        } elseif (is_bool($value)) {
96 1
            $valueExpr = new ConstFetch(new Name($value ? 'true' : 'false'));
97
        } else {
98 1
            $valueExpr = new ConstFetch(new Name('null'));
99
        }
100
101 11
        return new MethodCall($expr, 'scalarNode', [new Arg($valueExpr)]);
102
    }
103
104
    /**
105
     * @param Expr $queryBuilder
106
     * @param Expr $expr
107
     * @param array $data
108
     * @return Expr
109
     */
110 2
    private function appendChildrenToArrayNode(Expr $queryBuilder, Expr $expr, array $data)
111
    {
112 2
        foreach ($data as $value) {
113 2 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...
114 1
                $argument = $this->createObjectNode($queryBuilder);
115 2
            } elseif (is_array($value)) {
116 1
                $argument = $this->createArrayNode($queryBuilder);
117
            } else {
118 1
                $argument = $this->createScalarNode($queryBuilder, $value);
119
            }
120
121 2
            $expr = new MethodCall($expr, 'addToArrayNode', [new Arg($argument)]);
122
123 2 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...
124 1
                $expr = new MethodCall($this->appendChildrenToObjectNode($queryBuilder, $expr, $value), 'end');
125 2
            } elseif (is_array($value)) {
126 2
                $expr = new MethodCall($this->appendChildrenToArrayNode($queryBuilder, $expr, $value), 'end');
127
            }
128
        }
129
130 2
        return $expr;
131
    }
132
133
    /**
134
     * @param Expr $queryBuilder
135
     * @param Expr $expr
136
     * @param \stdClass $data
137
     * @return Expr
138
     */
139 12
    private function appendChildrenToObjectNode(Expr $queryBuilder, Expr $expr, \stdClass $data)
140
    {
141 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...
142 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...
143 12
                $argument = $this->createObjectNode($queryBuilder);
144 11
            } elseif (is_array($value)) {
145 2
                $argument = $this->createArrayNode($queryBuilder);
146
            } else {
147 11
                $argument = $this->createScalarNode($queryBuilder, $value);
148
            }
149
150 12
            $expr = new MethodCall($expr, 'addToObjectNode', [new Arg(new String_($key)), new Arg($argument)]);
151
152 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...
153 12
                $expr = new MethodCall($this->appendChildrenToObjectNode($queryBuilder, $expr, $value), 'end');
154 11
            } elseif (is_array($value)) {
155 12
                $expr = new MethodCall($this->appendChildrenToArrayNode($queryBuilder, $expr, $value), 'end');
156
            }
157
        }
158
159 12
        return $expr;
160
    }
161
162
    /**
163
     * @param string $code
164
     * @return string
165
     */
166 12
    private function structureCode(string $code): string
167
    {
168 12
        $lines = $this->getLinesByCode($code);
169
170 12
        $position = 0;
171
172 12
        $structuredLines = [];
173
174 12
        foreach ($lines as $i => $line) {
175 12
            $lastLine = $lines[$i-1] ?? '';
176 12
            $this->structuredLine($line, $lastLine, $position, $structuredLines);
177
        }
178
179 12
        return implode("\n", $structuredLines);
180
    }
181
182
    /**
183
     * @param string $code
184
     * @return array
185
     */
186 12
    private function getLinesByCode(string $code): array
187
    {
188 12
        $codeWithLinebreaks = str_replace('->add', "\n->add", $code);
189 12
        $codeWithLinebreaks = str_replace('->end', "\n->end", $codeWithLinebreaks);
190
191 12
        return explode("\n", $codeWithLinebreaks);
192
    }
193
194
    /**
195
     * @param string $line
196
     * @param string $lastLine
197
     * @param int $position
198
     * @param array $structuredLines
199
     */
200 12
    private function structuredLine(string $line, string $lastLine, int &$position, array &$structuredLines)
201
    {
202 12
        if (0 === strpos($line, '->add')) {
203 12
            $this->structureAddLine($line, $lastLine, $position, $structuredLines);
204 12
        } elseif (0 === strpos($line, '->end')) {
205 12
            $this->structureEndLine($line, $lastLine, $position, $structuredLines);
206
        } else {
207 12
            $structuredLines[] = $line;
208
        }
209 12
    }
210
211
    /**
212
     * @param string $line
213
     * @param string $lastLine
214
     * @param int $position
215
     * @param array $structuredLines
216
     */
217 12
    private function structureAddLine(string $line, string $lastLine, int &$position, array &$structuredLines)
218
    {
219 12
        if (false === strpos($lastLine, '->end') && false === strpos($lastLine, '->scalarNode')) {
220 12
            $position++;
221
        }
222
223 12
        $structuredLines[] = str_pad('', $position * 4) . $line;
224 12
    }
225
226
    /**
227
     * @param string $line
228
     * @param string $lastLine
229
     * @param int $position
230
     * @param array $structuredLines
231
     */
232 12
    private function structureEndLine(string $line, string $lastLine, int &$position, array &$structuredLines)
233
    {
234 12
        if (strpos($lastLine, '->objectNode') || strpos($lastLine, '->arrayNode')) {
235 1
            $structuredLines[count($structuredLines) - 1] .= '->end()';
236
237 1
            return;
238
        }
239
240 12
        $position--;
241
242 12
        $structuredLines[] = str_pad('', $position * 4) . $line;
243 12
    }
244
}
245