Completed
Push — master ( 1b389e...c3c10e )
by Dominik
02:16
created

QueryBuilderGenerator   B

Complexity

Total Complexity 40

Size/Duplication

Total Lines 224
Duplicated Lines 12.5 %

Coupling/Cohesion

Components 1
Dependencies 11

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 40
lcom 1
cbo 11
dl 28
loc 224
ccs 89
cts 89
cp 1
rs 8.2608
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A generateByJson() 0 16 2
A createQueryBuilderNode() 0 4 1
A createObjectNode() 0 4 1
A createArrayNode() 0 4 1
B createScalarNode() 0 14 6
C appendChildrenToArrayNode() 12 24 7
C appendChildrenToObjectNode() 12 24 7
A structureCode() 4 15 2
A getLinesByCode() 0 7 1
C structuredLine() 0 34 11

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like QueryBuilderGenerator often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use QueryBuilderGenerator, and based on these observations, apply Extract Interface, too.

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, 'nullNode');
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, '->nullNode') &&
217 13
                false === strpos($lastLine, '->stringNode')
218
            ) {
219 13
                $position++;
220
            }
221
222 13
            $structuredLines[] = str_pad('', $position * 4) . $line;
223
224 13
            return;
225
        }
226
227 13
        if (0 === strpos($line, '->end')) {
228 13
            if (strpos($lastLine, '->objectNode') || strpos($lastLine, '->arrayNode')) {
229 1
                $structuredLines[count($structuredLines) - 1] .= '->end()';
230
231 1
                return;
232
            }
233
234 13
            $position--;
235
236 13
            $structuredLines[] = str_pad('', $position * 4) . $line;
237
238 13
            return;
239
        }
240
241 13
        $structuredLines[] = $line;
242 13
    }
243
}
244