Completed
Push — master ( 672d1e...1b389e )
by Dominik
02:24
created

QueryBuilderGenerator::appendChildrenToArrayNode()   C

Complexity

Conditions 7
Paths 19

Size

Total Lines 24
Code Lines 15

Duplication

Lines 12
Ratio 50 %

Code Coverage

Tests 14
CRAP Score 7

Importance

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