Completed
Push — master ( d10f54...00ced4 )
by
unknown
05:32
created

DefinitionGenerator::generateClass()   C

Complexity

Conditions 8
Paths 26

Size

Total Lines 78
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 36
CRAP Score 8

Importance

Changes 0
Metric Value
dl 0
loc 78
rs 6.102
c 0
b 0
f 0
ccs 36
cts 36
cp 1
cc 8
eloc 36
nc 26
nop 1
crap 8

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php declare(strict_types=1);
0 ignored issues
show
Coding Style introduced by
End of line character is invalid; expected "\n" but found "\r\n"
Loading history...
2
/**
3
 * Created by Ruslan Molodyko.
4
 * Date: 09.09.2016
5
 * Time: 7:19
6
 */
7
namespace samsonframework\container\definition\builder;
8
9
use samsonframework\container\definition\ClassDefinition;
10
use samsonframework\container\definition\builder\exception\ReferenceNotImplementsException;
11
use samsonframework\container\definition\MethodDefinition;
12
use samsonframework\container\definition\PropertyDefinition;
13
use samsonframework\container\definition\reference\BoolReference;
14
use samsonframework\container\definition\reference\ClassReference;
15
use samsonframework\container\definition\reference\CollectionReference;
16
use samsonframework\container\definition\reference\ConstantReference;
17
use samsonframework\container\definition\reference\FloatReference;
18
use samsonframework\container\definition\reference\IntegerReference;
19
use samsonframework\container\definition\reference\NullReference;
20
use samsonframework\container\definition\reference\ParameterReference;
21
use samsonframework\container\definition\reference\ReferenceInterface;
22
use samsonframework\container\definition\reference\ServiceReference;
23
use samsonframework\container\definition\reference\StringReference;
24
use samsonframework\generator\ClassGenerator;
25
26
/**
27
 * Class DefinitionCompiler
28
 *
29
 * @author Ruslan Molodyko <[email protected]>
30
 */
31
class DefinitionGenerator
32
{
33
    /** @var  ClassGenerator */
34
    protected $generator;
35
36
    /**
37
     * DefinitionCompiler constructor.
38
     *
39
     * @param ClassGenerator $classGenerator
40
     */
41 4
    public function __construct(ClassGenerator $classGenerator)
42
    {
43 4
        $this->generator = $classGenerator;
44 4
    }
45
46
    /**
47
     * Get class generator
48
     *
49
     * @return ClassGenerator
50
     */
51 2
    public function getClassGenerator()
52
    {
53 2
        return $this->generator;
54
    }
55
56
    /**
57
     * Compile and get container
58
     *
59
     * @param DefinitionBuilder $definitionBuilder
60
     * @return string Get container code
61
     * @throws ReferenceNotImplementsException
62
     * @throws \InvalidArgumentException
63
     */
64 2
    public function generateClass(DefinitionBuilder $definitionBuilder): string
65
    {
66
        // Generate parameters if exists
67 2
        if (count($definitionBuilder->getParameterCollection())) {
68 1
            $parameterMethodGenerator = $this->generator
69 1
                ->defMethod('parameter')
70 1
                ->defProtected()
71 1
                ->defArgument('parameterName');
72
73 1
            $isFirstCondition = true;
74
            // Generate parameters
75 1
            foreach ($definitionBuilder->getParameterCollection() as $parameterName => $reference) {
76 1
                $parameterMethodGenerator->defLine(
77 1
                    $this->generateParameterCondition($parameterName, $reference, $isFirstCondition)
78
                );
79 1
                $isFirstCondition = false;
80
            }
81
82
            // Close method
83 1
            $parameterMethodGenerator->end();
84
        }
85
86 2
        $methodGenerator = $this->generator
87 2
            ->defMethod('logic')
88 2
            ->defProtected()
89 2
            ->defArgument('classNameOrServiceName')
90 2
            ->defLine('static $singletonCollection = [];')
91
        ;
92
93 2
        $isFirstCondition = true;
94
        /** @var ClassDefinition $classDefinition */
95 2
        foreach ($definitionBuilder->getDefinitionCollection() as $classDefinition) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
96
97 2
            $className = $classDefinition->getClassName();
98 2
            $serviceName = $classDefinition->getServiceName();
99
            // Name for static service collection
100 2
            $serviceId = $serviceName ?? $className;
101
102
            // Generate if condition by class name or value
103 2
            $methodGenerator->defLine($this->generateStartIfCondition($isFirstCondition, $className, $serviceName));
104
105
            // Generate static property service access if service is singleton
106
            // TODO Move this from if condition
107 2
            if ($classDefinition->isSingleton()) {
108 2
                $methodGenerator->defLine($this->generateStaticFunctionCall($serviceId));
109
            }
110
111
            // Generate constructor call if not
112 2
            $methodGenerator->defLine($this->generateConstructor($classDefinition));
113
114
            // Generate methods
115 2
            foreach ($classDefinition->getMethodsCollection() as $methodDefinition) {
116
                // Constructor is not a method skip it
117 2
                if ($methodDefinition->getMethodName() !== '__construct') {
118 2
                    $methodGenerator->defLine($this->generateSetters($classDefinition, $methodDefinition));
119
                }
120
            }
121
122
            // Generate properties
123 2
            foreach ($classDefinition->getPropertiesCollection() as $propertyDefinition) {
124
                // Generate properties
125 2
                $methodGenerator->defLine($this->generateProperty($classDefinition, $propertyDefinition));
126
            }
127
128
            // Generate return operator
129 2
            $methodGenerator->defLine($this->generateReturnOperator($classDefinition, $serviceId));
130
131
            // Close if
132 2
            $methodGenerator->defLine($this->generateEndIfCondition());
133
134 2
            $isFirstCondition = false;
135
        }
136
137
        // Close method
138 2
        $methodGenerator->end();
139
140 2
        return "<?php \n" . $this->generator->code();
141
    }
142
143
    /**
144
     * Generate start if parameter condition with smart if/else creation
145
     *
146
     * @param string $parameterName
147
     * @param ReferenceInterface $reference
148
     * @param bool $isFirstCondition
149
     * @return string
150
     * @throws ReferenceNotImplementsException
151
     */
152 1
    protected function generateParameterCondition(
153
        string $parameterName,
154
        ReferenceInterface $reference,
155
        bool $isFirstCondition = false
156
    ): string
157
    {
0 ignored issues
show
Coding Style introduced by
The closing parenthesis and the opening brace of a multi-line function declaration must be on the same line
Loading history...
158
        // If call this method first time then generate simple if or elseif construction
159 1
        $ifCondition = $isFirstCondition ? 'if' : 'elseif';
160 1
        return "$ifCondition (\$parameterName === '$parameterName') {\n" .
161 1
        "\t\t\treturn " . $this->resolveDependency($reference) . ";\n" .
162 1
        "\t\t}";
163
    }
164
165
    /**
166
     * Generate start if condition with smart if/else creation
167
     *
168
     * @param bool $isFirstCondition
169
     * @param string $className
170
     * @param string|null $serviceName
171
     * @return string
172
     */
173 2
    protected function generateStartIfCondition(bool $isFirstCondition, string $className, string $serviceName = null): string
174
    {
175
        // If call this method first time then generate simple if or elseif construction
176 2
        $ifCondition = $isFirstCondition ? 'if' : 'elseif';
177
        // If service name exists then add it to condition
178 2
        $serviceCondition = $serviceName ? "\$classNameOrServiceName === '$serviceName' || " : '';
179 2
        return "$ifCondition ($serviceCondition\$classNameOrServiceName === '$className') {";
180
    }
181
182
    /**
183
     * Close if condition
184
     *
185
     * @return string
186
     */
187 2
    protected function generateEndIfCondition(): string
188
    {
189 2
        return '}';
190
    }
191
192
    /**
193
     * When this service is singleton then create if with call already defined instance
194
     *
195
     * @param string $id Class or service name
196
     * @return string
197
     */
198 2
    protected function generateStaticFunctionCall(string $id): string
199
    {
200 2
        return "\tif (array_key_exists('$id', \$singletonCollection)) " .
201 2
        "{\n\t\t\t\treturn  \$singletonCollection['$id'];\n\t\t\t}";
202
    }
203
204
    /**
205
     * Generate constructor for service
206
     *
207
     * @param ClassDefinition $classDefinition
208
     * @return string
209
     * @throws ReferenceNotImplementsException
210
     */
211 2
    protected function generateConstructor(ClassDefinition $classDefinition): string
212
    {
213
        // TODO Fix adding slash before namespace
214 2
        $className = '\\' . $classDefinition->getClassName();
215 2
        $arguments = '';
216 2
        if (array_key_exists('__construct', $classDefinition->getMethodsCollection())) {
217 2
            $arguments .= $this->generateArguments($classDefinition->getMethodsCollection()['__construct']);
218
        }
219 2
        return "\t\$temp = new $className($arguments);";
220
    }
221
222
    /**
223
     * Generate arguments for method
224
     *
225
     * @param MethodDefinition $methodDefinition
226
     * @return string
227
     * @throws ReferenceNotImplementsException
228
     */
229 2
    protected function generateArguments(MethodDefinition $methodDefinition): string
230
    {
231 2
        $parameterCollection = $methodDefinition->getParametersCollection();
232
233
        // If arguments more than one then generate this ones on new line
234 2
        $newLinePrefix = count($parameterCollection) > 1 ? "\n\t\t\t\t" : '';
235
236 2
        $arguments = '';
237
        // Iterate all parameters and generate arguments
238 2
        foreach ($parameterCollection as $parameterDefinition) {
239 2
            $dependencyValue = $this->resolveDependency($parameterDefinition->getDependency());
240 2
            $arguments .= $newLinePrefix . "$dependencyValue,";
241
        }
242
        // Remove comma
243 2
        if (count($parameterCollection)) {
244 2
            $arguments = rtrim($arguments, ',');
245
        }
246
        // Add tabs
247 2
        if (count($parameterCollection) > 1) {
248 2
            $arguments .= "\n\t\t\t";
249
        }
250 2
        return $arguments;
251
    }
252
253
    /**
254
     * Generate setters for class
255
     *
256
     * @param ClassDefinition $classDefinition
257
     * @param MethodDefinition $methodDefinition
258
     * @return string
259
     * @throws ReferenceNotImplementsException
260
     */
261 2
    protected function generateSetters(ClassDefinition $classDefinition, MethodDefinition $methodDefinition): string
262
    {
263 2
        $className = $classDefinition->getClassName();
264 2
        $methodName = $methodDefinition->getMethodName();
265 2
        $arguments = $this->generateArguments($methodDefinition);
266
        // Call method by reflection
267 2
        if (!$methodDefinition->isPublic()) {
268 2
            $isEmptyArguments = count($methodDefinition->getParametersCollection()) === 0;
269 2
            return "\t\$method = (new \\ReflectionClass('$className'))->getMethod('$methodName');" .
270 2
            "\n\t\t\t\$method->setAccessible(true);" .
271 2
            "\n\t\t\t\$method->invoke(\$temp" . ($isEmptyArguments ? '' : ", $arguments") . ');' .
272 2
            "\n\t\t\t\$method->setAccessible(false);";
273
        } else {
274
            return "\t\$temp->$methodName($arguments);";
275
        }
276
    }
277
278
    /**
279
     * Generate property for class
280
     *
281
     * @param PropertyDefinition $propertyDefinition
282
     * @param ClassDefinition $classDefinition
283
     * @return string
284
     * @throws ReferenceNotImplementsException
285
     */
286 2
    protected function generateProperty(ClassDefinition $classDefinition, PropertyDefinition $propertyDefinition): string
287
    {
288 2
        $dependencyValue = $this->resolveDependency($propertyDefinition->getDependency());
289 2
        $propertyName = $propertyDefinition->getPropertyName();
290 2
        $className = $classDefinition->getClassName();
291
292 2
        if ($propertyDefinition->isPublic()) {
293 2
            return "\t\$temp->$propertyName = $dependencyValue;";
294
            // Use reflection to setting the value
295
        } else {
296 2
            return "\t\$property = (new \\ReflectionClass('$className'))->getProperty('$propertyName');" .
297 2
                "\n\t\t\t\$property->setAccessible(true);" .
298 2
                "\n\t\t\t\$property->setValue(\$temp, $dependencyValue);" .
299 2
                "\n\t\t\t\$property->setAccessible(false);";
300
        }
301
    }
302
303
    /**
304
     * Generate return operator and save instance to static properties it its singleton
305
     *
306
     * @param ClassDefinition $classDefinition
307
     * @param string $id
308
     * @return string
309
     */
310 2
    protected function generateReturnOperator(ClassDefinition $classDefinition, string $id = null): string
311
    {
312 2
        $code = "\treturn \$temp;";
313
        // If there is a singleton then save it to static service collection
314 2
        if ($classDefinition->isSingleton()) {
315 2
            $code = "\t\$singletonCollection['$id'] = \$temp;\n\t\t" . $code;
316
        }
317 2
        return $code;
318
    }
319
320
    /**
321
     * Resolve ReferenceInterface argument into value
322
     *
323
     * @param ReferenceInterface $reference
324
     * @return string
325
     * @throws ReferenceNotImplementsException
326
     */
327 2
    protected function resolveDependency(ReferenceInterface $reference): string
328
    {
329 2
        if ($reference instanceof ClassReference) {
330 2
            $value = $reference->getClassName();
331 2
            return "\$this->logic('$value')";
332
        } elseif ($reference instanceof NullReference) {
333
            return 'null';
334 2
        } elseif ($reference instanceof ServiceReference) {
335
            $value = $reference->getName();
336
            return "\$this->logic('$value')";
337
        } elseif ($reference instanceof StringReference) {
338 2
            $value = $reference->getValue();
339 2
            return "'$value'";
340
        } elseif ($reference instanceof FloatReference) {
341
            $value = $reference->getValue();
342
            return "$value";
343
        } elseif ($reference instanceof BoolReference) {
344 1
            $value = $reference->getValue();
345 1
            return "$value";
346
        } elseif ($reference instanceof IntegerReference) {
347 2
            $value = $reference->getValue();
348 2
            return "$value";
349 1
        } elseif ($reference instanceof ParameterReference) {
350 1
            $value = $reference->getParameterName();
351 1
            return "\$this->parameter('$value')";
352
        } elseif ($reference instanceof ConstantReference) {
353 2
            $value = $reference->getValue();
354 2
            return "$value";
355
        } elseif ($reference instanceof CollectionReference) {
356
            /** @var array $value */
357 2
            $value = $reference->getCollection();
358 2
            $string = '[';
359
            // Iterate items
360 2
            foreach ($value as $item) {
0 ignored issues
show
Bug introduced by
The expression $value of type array|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
361 2
                $value = $this->resolveDependency($item->getValue());
362 2
                $key = $this->resolveDependency($item->getKey());
363 2
                $string .= "$key => $value, ";
364
            }
365
            // Remove comma
366 2
            $string = rtrim($string, ', ');
367 2
            return $string . ']';
368
        } else {
369
            throw new ReferenceNotImplementsException(sprintf(
370
                'Class "%s" does not have correct implementation in generator', get_class($reference)
371
            ));
372
        }
373
    }
374
}
375