Completed
Push — master ( 7ae81a...efb313 )
by Dominik
02:22
created

QueryBuilderGenerator::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Saxulum\ElasticSearchQueryBuilder;
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\Name\FullyQualified;
16
use PhpParser\Node\Scalar\DNumber;
17
use PhpParser\Node\Scalar\LNumber;
18
use PhpParser\Node\Scalar\String_;
19
use PhpParser\PrettyPrinter\Standard as PhpGenerator;
20
21
final class QueryBuilderGenerator
22
{
23
    /**
24
     * @var PhpGenerator
25
     */
26
    private $phpGenerator;
27
28
    /**
29
     * @param PhpGenerator $phpGenerator
30
     */
31
    public function __construct(PhpGenerator $phpGenerator)
32
    {
33
        $this->phpGenerator = $phpGenerator;
34
    }
35
36
    /**
37
     * @param $query
38
     * @return string
39
     */
40
    public function generateByJson($query): string
41
    {
42
        $data = json_decode($query, false);
43
        if (JSON_ERROR_NONE !== json_last_error()) {
44
            throw new \InvalidArgumentException(sprintf('Message: %s, query: %s', json_last_error_msg(), $query));
45
        }
46
47
        $stmts = [$this->createQueryBuilderNode()];
48
49
        $queryBuilder = new Variable('queryBuilder');
50
51
        if ($data instanceof \stdClass) {
52
            $stmts[] = $this->appendChildrenToObjectNode($queryBuilder, $queryBuilder, $data);
53
        } else {
54
            $stmts[] = $this->appendChildrenToArrayNode($queryBuilder, $queryBuilder, $data);
55
        }
56
57
        return $this->structureCode($this->phpGenerator->prettyPrint($stmts));
58
    }
59
60
    /**
61
     * @param string $code
62
     * @return string
63
     */
64
    private function structureCode(string $code): string
65
    {
66
        $codeWithLinebreaks = str_replace('->add', "\n->add", $code);
67
        $codeWithLinebreaks = str_replace('->end', "\n->end", $codeWithLinebreaks);
68
69
        $lines = explode("\n", $codeWithLinebreaks);
70
71
        $position = 0;
72
73
        $structuredLines = [];
74
75
        foreach ($lines as $i => $line) {
76
            $lastLine = $lines[$i-1] ?? '';
77
            if (0 === strpos($line, '->add')) {
78
                if (false === strpos($lastLine, '->end') && false === strpos($lastLine, '->scalarNode')) {
79
                    $position++;
80
                }
81
                $structuredLines[] = str_pad('', $position * 4) . $line;
82
            } elseif (0 === strpos($line, '->end')) {
83
                $position--;
84
                $structuredLines[] = str_pad('', $position * 4) . $line;
85
            } else {
86
                $structuredLines[] = $line;
87
            }
88
        }
89
90
        return implode("\n", $structuredLines);
91
    }
92
93
    /**
94
     * @return Expr
95
     */
96
    private function createQueryBuilderNode(): Expr
97
    {
98
        return new Assign(new Variable('queryBuilder'), new New_(new FullyQualified(QueryBuilder::class)));
99
    }
100
101
    /**
102
     * @param Expr $expr
103
     * @return Expr
104
     */
105
    private function createObjectNode(Expr $expr): Expr
106
    {
107
        return new MethodCall($expr, 'objectNode');
108
    }
109
110
    /**
111
     * @param Expr $expr
112
     * @return Expr
113
     */
114
    private function createArrayNode(Expr $expr): Expr
115
    {
116
        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
    private function createScalarNode(Expr $expr, $value): Expr
125
    {
126
        if (is_string($value)) {
127
            $valueExpr = new String_($value);
128
        } elseif (is_int($value)) {
129
            $valueExpr = new LNumber($value);
130
        } elseif (is_float($value)) {
131
            $valueExpr = new DNumber($value);
132
        } elseif (is_bool($value)) {
133
            $valueExpr = new ConstFetch(new Name($value ? 'true' : 'false'));
134
        } else {
135
            $valueExpr = new ConstFetch(new Name('null'));
136
        }
137
138
        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
    private function appendChildrenToArrayNode(Expr $queryBuilder, Expr $expr, array $data)
148
    {
149
        foreach ($data as $key => $value) {
150
            if ($value instanceof \stdClass) {
151
                $arguments = [new Arg($this->createObjectNode($queryBuilder))];
152
            } elseif (is_array($value)) {
153
                $arguments = [new Arg($this->createArrayNode($queryBuilder))];
154
            } else {
155
                $arguments = [new Arg($this->createScalarNode($queryBuilder, $value))];
156
            }
157
158
            $expr = new MethodCall($expr, 'addToArrayNode', $arguments);
159
160 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
                $expr = new MethodCall($this->appendChildrenToObjectNode($queryBuilder, $expr, $value), 'end');
162
            } elseif (is_array($value)) {
163
                $expr = new MethodCall($this->appendChildrenToArrayNode($queryBuilder, $expr, $value), 'end');
164
            }
165
        }
166
167
        return $expr;
168
    }
169
170
    /**
171
     * @param Expr $expr
172
     * @param \stdClass $data
173
     * @return Expr
174
     */
175
    private function appendChildrenToObjectNode(Expr $queryBuilder, Expr $expr, \stdClass $data)
176
    {
177
        foreach ($data as $key => $value) {
0 ignored issues
show
Bug introduced by
The expression $data of type object<stdClass> is not traversable.
Loading history...
178
            if ($value instanceof \stdClass) {
179
                $arguments = [new Arg(new String_($key)), new Arg($this->createObjectNode($queryBuilder))];
180
            } elseif (is_array($value)) {
181
                $arguments = [new Arg(new String_($key)), new Arg($this->createArrayNode($queryBuilder))];
182
            } else {
183
                $arguments = [new Arg(new String_($key)), new Arg($this->createScalarNode($queryBuilder, $value))];
184
            }
185
186
            $expr = new MethodCall($expr, 'addToObjectNode', $arguments);
187
188 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...
189
                $expr = new MethodCall($this->appendChildrenToObjectNode($queryBuilder, $expr, $value), 'end');
190
            } elseif (is_array($value)) {
191
                $expr = new MethodCall($this->appendChildrenToArrayNode($queryBuilder, $expr, $value), 'end');
192
            }
193
        }
194
195
        return $expr;
196
    }
197
}
198