Completed
Push — master ( cfbb4d...458589 )
by Dominik
02:35
created

QueryBuilderGenerator::generateByJson()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 9
cts 9
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 9
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
     * @param string $code
58
     * @return string
59
     */
60 12
    private function structureCode(string $code): string
61
    {
62 12
        $codeWithLinebreaks = str_replace('->add', "\n->add", $code);
63 12
        $codeWithLinebreaks = str_replace('->end', "\n->end", $codeWithLinebreaks);
64
65 12
        $lines = explode("\n", $codeWithLinebreaks);
66
67 12
        $position = 0;
68
69 12
        $structuredLines = [];
70
71 12
        foreach ($lines as $i => $line) {
72 12
            $lastLine = $lines[$i-1] ?? '';
73 12
            if (0 === strpos($line, '->add')) {
74 12
                if (false === strpos($lastLine, '->end') && false === strpos($lastLine, '->scalarNode')) {
75 12
                    $position++;
76
                }
77 12
                $structuredLines[] = str_pad('', $position * 4) . $line;
78 12
            } elseif (0 === strpos($line, '->end')) {
79 12
                if (strpos($lastLine, '->objectNode') || strpos($lastLine, '->arrayNode')) {
80 1
                    $structuredLines[count($structuredLines) - 1] .= '->end()';
81
                } else {
82 12
                    $position--;
83 12
                    $structuredLines[] = str_pad('', $position * 4) . $line;
84
                }
85
            } else {
86 12
                $structuredLines[] = $line;
87
            }
88
        }
89
90 12
        return implode("\n", $structuredLines);
91
    }
92
93
    /**
94
     * @return Expr
95
     */
96 12
    private function createQueryBuilderNode(): Expr
97
    {
98 12
        return new Assign(new Variable('queryBuilder'), new New_(new Name('QueryBuilder')));
99
    }
100
101
    /**
102
     * @param Expr $expr
103
     * @return Expr
104
     */
105 12
    private function createObjectNode(Expr $expr): Expr
106
    {
107 12
        return new MethodCall($expr, 'objectNode');
108
    }
109
110
    /**
111
     * @param Expr $expr
112
     * @return Expr
113
     */
114 2
    private function createArrayNode(Expr $expr): Expr
115
    {
116 2
        return new MethodCall($expr, 'arrayNode');
117
    }
118
119
    /**
120
     * @param Expr $expr
121
     * @param string|float|int|bool|null $value
122
     * @return Expr
123
     */
124 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...
125
    {
126 11
        if (is_string($value)) {
127 10
            $valueExpr = new String_($value);
128 4
        } elseif (is_int($value)) {
129 4
            $valueExpr = new LNumber($value);
130 1
        } elseif (is_float($value)) {
131 1
            $valueExpr = new DNumber($value);
132 1
        } elseif (is_bool($value)) {
133 1
            $valueExpr = new ConstFetch(new Name($value ? 'true' : 'false'));
134
        } else {
135 1
            $valueExpr = new ConstFetch(new Name('null'));
136
        }
137
138 11
        return new MethodCall($expr, 'scalarNode', [new Arg($valueExpr)]);
139
    }
140
141
    /**
142
     * @param Expr $queryBuilder
143
     * @param Expr $expr
144
     * @param array $data
145
     * @return Expr
146
     */
147 2
    private function appendChildrenToArrayNode(Expr $queryBuilder, Expr $expr, array $data)
148
    {
149 2
        foreach ($data as $value) {
150 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...
151 1
                $argument = $this->createObjectNode($queryBuilder);
152 2
            } elseif (is_array($value)) {
153 1
                $argument = $this->createArrayNode($queryBuilder);
154
            } else {
155 1
                $argument = $this->createScalarNode($queryBuilder, $value);
156
            }
157
158 2
            $expr = new MethodCall($expr, 'addToArrayNode', [new Arg($argument)]);
159
160 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...
161 1
                $expr = new MethodCall($this->appendChildrenToObjectNode($queryBuilder, $expr, $value), 'end');
162 2
            } elseif (is_array($value)) {
163 2
                $expr = new MethodCall($this->appendChildrenToArrayNode($queryBuilder, $expr, $value), 'end');
164
            }
165
        }
166
167 2
        return $expr;
168
    }
169
170
    /**
171
     * @param Expr $queryBuilder
172
     * @param Expr $expr
173
     * @param \stdClass $data
174
     * @return Expr
175
     */
176 12
    private function appendChildrenToObjectNode(Expr $queryBuilder, Expr $expr, \stdClass $data)
177
    {
178 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...
179 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...
180 12
                $argument = $this->createObjectNode($queryBuilder);
181 11
            } elseif (is_array($value)) {
182 2
                $argument = $this->createArrayNode($queryBuilder);
183
            } else {
184 11
                $argument = $this->createScalarNode($queryBuilder, $value);
185
            }
186
187 12
            $expr = new MethodCall($expr, 'addToObjectNode', [new Arg(new String_($key)), new Arg($argument)]);
188
189 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...
190 12
                $expr = new MethodCall($this->appendChildrenToObjectNode($queryBuilder, $expr, $value), 'end');
191 11
            } elseif (is_array($value)) {
192 12
                $expr = new MethodCall($this->appendChildrenToArrayNode($queryBuilder, $expr, $value), 'end');
193
            }
194
        }
195
196 12
        return $expr;
197
    }
198
}
199