Completed
Push — master ( 00ced4...d3edd8 )
by
unknown
07:54
created

DefinitionGenerator   D

Complexity

Total Complexity 49

Size/Duplication

Total Lines 396
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 19

Test Coverage

Coverage 84.81%

Importance

Changes 0
Metric Value
wmc 49
lcom 1
cbo 19
dl 0
loc 396
ccs 134
cts 158
cp 0.8481
rs 4.6787
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A getClassGenerator() 0 4 1
D generateClass() 0 99 11
A generateScopeList() 0 17 3
A generateParameterCondition() 0 12 2
A generateStartIfCondition() 0 15 3
A generateEndIfCondition() 0 4 1
A generateStaticFunctionCall() 0 5 1
A generateConstructor() 0 9 2
B generateArguments() 0 23 5
A generateSetters() 0 16 3
A generateProperty() 0 16 2
A generateReturnOperator() 0 9 2
C resolveDependency() 0 47 12

How to fix   Complexity   

Complex Class

Complex classes like DefinitionGenerator 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 DefinitionGenerator, and based on these observations, apply Extract Interface, too.

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
use samsonframework\generator\MethodGenerator;
26
27
/**
28
 * Class DefinitionCompiler
29
 *
30
 * @author Ruslan Molodyko <[email protected]>
31
 */
32
class DefinitionGenerator
33
{
34
    /** @var  ClassGenerator */
35
    protected $generator;
36
37
    /**
38
     * DefinitionCompiler constructor.
39
     *
40
     * @param ClassGenerator $classGenerator
41
     */
42 4
    public function __construct(ClassGenerator $classGenerator)
43
    {
44 4
        $this->generator = $classGenerator;
45 4
    }
46
47
    /**
48
     * Get class generator
49
     *
50
     * @return ClassGenerator
51
     */
52 2
    public function getClassGenerator()
53
    {
54 2
        return $this->generator;
55
    }
56
57
    /**
58
     * Compile and get container
59
     *
60
     * @param DefinitionBuilder $definitionBuilder
61
     * @return string Get container code
62
     * @throws ReferenceNotImplementsException
63
     * @throws \InvalidArgumentException
64
     */
65 2
    public function generateClass(DefinitionBuilder $definitionBuilder): string
66
    {
67
        // Generate parameters if exists
68 2
        if (count($definitionBuilder->getParameterCollection())) {
69 1
            $parameterMethodGenerator = $this->generator
70 1
                ->defMethod('parameter')
71 1
                ->defProtected()
72 1
                ->defArgument('parameterName');
73
74 1
            $isFirstCondition = true;
75
            // Generate parameters
76 1
            foreach ($definitionBuilder->getParameterCollection() as $parameterName => $reference) {
77 1
                $parameterMethodGenerator->defLine(
78 1
                    $this->generateParameterCondition($parameterName, $reference, $isFirstCondition)
79
                );
80 1
                $isFirstCondition = false;
81
            }
82
83
            // Close method
84 1
            $parameterMethodGenerator->end();
85
        }
86
87 2
        $methodGenerator = $this->generator
88 2
            ->defMethod('logic')
89 2
            ->defProtected()
90 2
            ->defArgument('classNameOrServiceName')
91 2
            ->defLine('static $singletonCollection = [];')
92
        ;
93
94 2
        $scopes = [];
95 2
        $isFirstCondition = true;
96
        /** @var ClassDefinition $classDefinition */
97 2
        foreach ($definitionBuilder->getDefinitionCollection() as $classDefinition) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
98
99
            // Get scopes
100 2
            foreach ($classDefinition->getScopes() as $scope) {
101
                $id = $scope::getId();
102
                if (!array_key_exists($id, $scopes)) {
103
                    $scopes[$id] = [];
104
                }
105
                // Store definition id
106
                $scopes[$id][] = $classDefinition->getServiceName() ?: $classDefinition->getClassName();
107
            }
108
109 2
            $className = $classDefinition->getClassName();
110 2
            $serviceName = $classDefinition->getServiceName();
111
            // Name for static service collection
112 2
            $serviceId = $serviceName ?? $className;
113
114
            // Generate if condition by class name or value
115 2
            $methodGenerator->defLine($this->generateStartIfCondition($isFirstCondition, $className, $serviceName));
116
117
            // Generate static property service access if service is singleton
118
            // TODO Move this from if condition
119 2
            if ($classDefinition->isSingleton()) {
120 2
                $methodGenerator->defLine($this->generateStaticFunctionCall($serviceId));
121
            }
122
123
            // Generate constructor call if not
124 2
            $methodGenerator->defLine($this->generateConstructor($classDefinition));
125
126
            // Generate methods
127 2
            foreach ($classDefinition->getMethodsCollection() as $methodDefinition) {
128
                // Constructor is not a method skip it
129 2
                if ($methodDefinition->getMethodName() !== '__construct') {
130 2
                    $methodGenerator->defLine($this->generateSetters($classDefinition, $methodDefinition));
131
                }
132
            }
133
134
            // Generate properties
135 2
            foreach ($classDefinition->getPropertiesCollection() as $propertyDefinition) {
136
                // Generate properties
137 2
                $methodGenerator->defLine($this->generateProperty($classDefinition, $propertyDefinition));
138
            }
139
140
            // Generate return operator
141 2
            $methodGenerator->defLine($this->generateReturnOperator($classDefinition, $serviceId));
142
143
            // Close if
144 2
            $methodGenerator->defLine($this->generateEndIfCondition());
145
146 2
            $isFirstCondition = false;
147
        }
148
149
        // Close method
150 2
        $methodGenerator->end();
151
152
        // Generate constructor
153 2
        $constructorGenerator = $this->generator
154 2
            ->defMethod('__construct');
155
156
        // Generate scope list
157 2
        $this->generateScopeList($constructorGenerator, $scopes);
158
159
        // Close constructor
160 2
        $constructorGenerator->end();
161
162 2
        return "<?php \n" . $this->generator->code();
163
    }
164
165
    /**
166
     * Generate scope list
167
     *
168
     * @param MethodGenerator $constructorGenerator
169
     * @param array $scopes
170
     * @throws \InvalidArgumentException
171
     */
172 2
    protected function generateScopeList(MethodGenerator $constructorGenerator, array $scopes)
173
    {
174 2
        $constructorGenerator->defLine('$this->scopes = [');
175
        /**
176
         * @var string $scopeName
177
         * @var array $ids
178
         */
179 2
        foreach ($scopes as $scopeName => $ids) {
180
            $constructorGenerator->defLine("\t'$scopeName' => [");
181
            // Iterate service ids
182
            foreach ($ids as $id) {
183
                $constructorGenerator->defLine("\t\t'$id',");
184
            }
185
            $constructorGenerator->defLine("\t]");
186
        }
187 2
        $constructorGenerator->defLine('];');
188 2
    }
189
190
    /**
191
     * Generate start if parameter condition with smart if/else creation
192
     *
193
     * @param string $parameterName
194
     * @param ReferenceInterface $reference
195
     * @param bool $isFirstCondition
196
     * @return string
197
     * @throws ReferenceNotImplementsException
198
     */
199 1
    protected function generateParameterCondition(
200
        string $parameterName,
201
        ReferenceInterface $reference,
202
        bool $isFirstCondition = false
203
    ): string
204
    {
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...
205
        // If call this method first time then generate simple if or elseif construction
206 1
        $ifCondition = $isFirstCondition ? 'if' : 'elseif';
207 1
        return "$ifCondition (\$parameterName === '$parameterName') {\n" .
208 1
        "\t\t\treturn " . $this->resolveDependency($reference) . ";\n" .
209 1
        "\t\t}";
210
    }
211
212
    /**
213
     * Generate start if condition with smart if/else creation
214
     *
215
     * @param bool $isFirstCondition
216
     * @param string $className
217
     * @param string|null $serviceName
218
     * @return string
219
     */
220 2
    protected function generateStartIfCondition(
221
        bool $isFirstCondition,
222
        string $className,
223
        string $serviceName = null
224
    ): string
225
    {
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...
226
        // If call this method first time then generate simple if or elseif construction
227 2
        $ifCondition = $isFirstCondition ? 'if' : 'elseif';
228
        // If service name exists then add it to condition
229 2
        $serviceCondition = $serviceName ? "\$classNameOrServiceName === '$serviceName' || " : '';
230
        // Get class name without slash
231 2
        $classNameWithoutSlash = ltrim($className, '\\');
232 2
        return "$ifCondition ($serviceCondition\$classNameOrServiceName === '$className'" .
233 2
            " || \$classNameOrServiceName === '$classNameWithoutSlash') {";
234
    }
235
236
    /**
237
     * Close if condition
238
     *
239
     * @return string
240
     */
241 2
    protected function generateEndIfCondition(): string
242
    {
243 2
        return '}';
244
    }
245
246
    /**
247
     * When this service is singleton then create if with call already defined instance
248
     *
249
     * @param string $id Class or service name
250
     * @return string
251
     */
252 2
    protected function generateStaticFunctionCall(string $id): string
253
    {
254 2
        return "\tif (array_key_exists('$id', \$singletonCollection)) " .
255 2
        "{\n\t\t\t\treturn  \$singletonCollection['$id'];\n\t\t\t}";
256
    }
257
258
    /**
259
     * Generate constructor for service
260
     *
261
     * @param ClassDefinition $classDefinition
262
     * @return string
263
     * @throws ReferenceNotImplementsException
264
     */
265 2
    protected function generateConstructor(ClassDefinition $classDefinition): string
266
    {
267 2
        $className = $classDefinition->getClassName();
268 2
        $arguments = '';
269 2
        if (array_key_exists('__construct', $classDefinition->getMethodsCollection())) {
270 2
            $arguments .= $this->generateArguments($classDefinition->getMethodsCollection()['__construct']);
271
        }
272 2
        return "\t\$temp = new $className($arguments);";
273
    }
274
275
    /**
276
     * Generate arguments for method
277
     *
278
     * @param MethodDefinition $methodDefinition
279
     * @return string
280
     * @throws ReferenceNotImplementsException
281
     */
282 2
    protected function generateArguments(MethodDefinition $methodDefinition): string
283
    {
284 2
        $parameterCollection = $methodDefinition->getParametersCollection();
285
286
        // If arguments more than one then generate this ones on new line
287 2
        $newLinePrefix = count($parameterCollection) > 1 ? "\n\t\t\t\t" : '';
288
289 2
        $arguments = '';
290
        // Iterate all parameters and generate arguments
291 2
        foreach ($parameterCollection as $parameterDefinition) {
292 2
            $dependencyValue = $this->resolveDependency($parameterDefinition->getDependency());
293 2
            $arguments .= $newLinePrefix . "$dependencyValue,";
294
        }
295
        // Remove comma
296 2
        if (count($parameterCollection)) {
297 2
            $arguments = rtrim($arguments, ',');
298
        }
299
        // Add tabs
300 2
        if (count($parameterCollection) > 1) {
301 2
            $arguments .= "\n\t\t\t";
302
        }
303 2
        return $arguments;
304
    }
305
306
    /**
307
     * Generate setters for class
308
     *
309
     * @param ClassDefinition $classDefinition
310
     * @param MethodDefinition $methodDefinition
311
     * @return string
312
     * @throws ReferenceNotImplementsException
313
     */
314 2
    protected function generateSetters(ClassDefinition $classDefinition, MethodDefinition $methodDefinition): string
315
    {
316 2
        $className = $classDefinition->getClassName();
317 2
        $methodName = $methodDefinition->getMethodName();
318 2
        $arguments = $this->generateArguments($methodDefinition);
319
        // Call method by reflection
320 2
        if (!$methodDefinition->isPublic()) {
321 2
            $isEmptyArguments = count($methodDefinition->getParametersCollection()) === 0;
322 2
            return "\t\$method = (new \\ReflectionClass('$className'))->getMethod('$methodName');" .
323 2
            "\n\t\t\t\$method->setAccessible(true);" .
324 2
            "\n\t\t\t\$method->invoke(\$temp" . ($isEmptyArguments ? '' : ", $arguments") . ');' .
325 2
            "\n\t\t\t\$method->setAccessible(false);";
326
        } else {
327
            return "\t\$temp->$methodName($arguments);";
328
        }
329
    }
330
331
    /**
332
     * Generate property for class
333
     *
334
     * @param PropertyDefinition $propertyDefinition
335
     * @param ClassDefinition $classDefinition
336
     * @return string
337
     * @throws ReferenceNotImplementsException
338
     */
339 2
    protected function generateProperty(ClassDefinition $classDefinition, PropertyDefinition $propertyDefinition): string
340
    {
341 2
        $dependencyValue = $this->resolveDependency($propertyDefinition->getDependency());
342 2
        $propertyName = $propertyDefinition->getPropertyName();
343 2
        $className = $classDefinition->getClassName();
344
345 2
        if ($propertyDefinition->isPublic()) {
346
            return "\t\$temp->$propertyName = $dependencyValue;";
347
            // Use reflection to setting the value
348
        } else {
349 2
            return "\t\$property = (new \\ReflectionClass('$className'))->getProperty('$propertyName');" .
350 2
                "\n\t\t\t\$property->setAccessible(true);" .
351 2
                "\n\t\t\t\$property->setValue(\$temp, $dependencyValue);" .
352 2
                "\n\t\t\t\$property->setAccessible(false);";
353
        }
354
    }
355
356
    /**
357
     * Generate return operator and save instance to static properties it its singleton
358
     *
359
     * @param ClassDefinition $classDefinition
360
     * @param string $id
361
     * @return string
362
     */
363 2
    protected function generateReturnOperator(ClassDefinition $classDefinition, string $id = null): string
364
    {
365 2
        $code = "\treturn \$temp;";
366
        // If there is a singleton then save it to static service collection
367 2
        if ($classDefinition->isSingleton()) {
368 2
            $code = "\t\$singletonCollection['$id'] = \$temp;\n\t\t" . $code;
369
        }
370 2
        return $code;
371
    }
372
373
    /**
374
     * Resolve ReferenceInterface argument into value
375
     *
376
     * @param ReferenceInterface $reference
377
     * @return string
378
     * @throws ReferenceNotImplementsException
379
     */
380 2
    protected function resolveDependency(ReferenceInterface $reference): string
381
    {
382 2
        if ($reference instanceof ClassReference) {
383 2
            $value = $reference->getClassName();
384 2
            return "\$this->logic('$value')";
385
        } elseif ($reference instanceof NullReference) {
386
            return 'null';
387 2
        } elseif ($reference instanceof ServiceReference) {
388
            $value = $reference->getName();
389
            return "\$this->logic('$value')";
390
        } elseif ($reference instanceof StringReference) {
391 2
            $value = $reference->getValue();
392 2
            return "'$value'";
393
        } elseif ($reference instanceof FloatReference) {
394
            $value = $reference->getValue();
395
            return "$value";
396
        } elseif ($reference instanceof BoolReference) {
397 1
            $value = $reference->getValue();
398 1
            return "$value";
399
        } elseif ($reference instanceof IntegerReference) {
400 2
            $value = $reference->getValue();
401 2
            return "$value";
402 1
        } elseif ($reference instanceof ParameterReference) {
403 1
            $value = $reference->getParameterName();
404 1
            return "\$this->parameter('$value')";
405
        } elseif ($reference instanceof ConstantReference) {
406 2
            $value = $reference->getValue();
407 2
            return "$value";
408
        } elseif ($reference instanceof CollectionReference) {
409
            /** @var array $value */
410 2
            $value = $reference->getCollection();
411 2
            $string = '[';
412
            // Iterate items
413 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...
414 2
                $value = $this->resolveDependency($item->getValue());
415 2
                $key = $this->resolveDependency($item->getKey());
416 2
                $string .= "$key => $value, ";
417
            }
418
            // Remove comma
419 2
            $string = rtrim($string, ', ');
420 2
            return $string . ']';
421
        } else {
422
            throw new ReferenceNotImplementsException(sprintf(
423
                'Class "%s" does not have correct implementation in generator', get_class($reference)
424
            ));
425
        }
426
    }
427
}
428