Completed
Pull Request — master (#14)
by
unknown
08:12
created

StructureGenerator   C

Complexity

Total Complexity 34

Size/Duplication

Total Lines 283
Duplicated Lines 9.19 %

Coupling/Cohesion

Components 2
Dependencies 18

Importance

Changes 0
Metric Value
wmc 34
lcom 2
cbo 18
dl 26
loc 283
rs 6.7466
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A parseToArray() 0 11 2
A buildEntitiesClassStructure() 0 15 3
B prepareClassManager() 0 32 4
A generateAndFillClassMethods() 0 16 2
A appendPropertiesMethods() 13 16 3
A appendMethodsDerivedFromInterface() 0 14 4
A generateAndFillInterfaceMethods() 13 22 3
A prepareAndFillInitProperties() 0 18 3
A generateAndFillTestClassMethods() 0 11 2
A buildAndAppendTestMethodIfNecessary() 0 8 2
A deserializeJsonDataToClassManager() 0 4 1
A getSerializer() 0 4 1
A getParser() 0 4 1
A getDefaultClassConfigIfNeed() 0 8 2

How to fix   Duplicated Code   

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:

1
<?php
2
3
namespace SimpleEntityGeneratorBundle\Lib;
4
5
use Doctrine\Common\Collections\ArrayCollection;
6
use JMS\Serializer\SerializerBuilder;
7
use JMS\Serializer\SerializerInterface;
8
use SimpleEntityGeneratorBundle\Lib\Builders\DerivedMethodsBuilder;
9
use SimpleEntityGeneratorBundle\Lib\Items\ClassConstructorManager;
10
use SimpleEntityGeneratorBundle\Lib\Items\ClassManager;
11
use SimpleEntityGeneratorBundle\Lib\Items\InitPropertyManager;
12
use SimpleEntityGeneratorBundle\Lib\Items\InterfaceManager;
13
use SimpleEntityGeneratorBundle\Lib\Items\MethodDerivedFromInterfaceManager;
14
use SimpleEntityGeneratorBundle\Lib\Items\MethodForPropertyManager;
15
use SimpleEntityGeneratorBundle\Lib\Items\MethodGetterBooleanInterfaceManager;
16
use SimpleEntityGeneratorBundle\Lib\Items\MethodGetterBooleanManager;
17
use SimpleEntityGeneratorBundle\Lib\Items\MethodGetterInterfaceManager;
18
use SimpleEntityGeneratorBundle\Lib\Items\MethodGetterManager;
19
use SimpleEntityGeneratorBundle\Lib\Items\MethodManager;
20
use SimpleEntityGeneratorBundle\Lib\Items\MethodSetterInterfaceManager;
21
use SimpleEntityGeneratorBundle\Lib\Items\MethodSetterManager;
22
use SimpleEntityGeneratorBundle\Lib\Items\TestClassManager;
23
use SimpleEntityGeneratorBundle\Lib\Items\TestMethodManager;
24
use Symfony\Component\Yaml\Parser;
25
26
/**
27
 * GeneratorEntity Manager
28
 *
29
 * @author Sławomir Kania <[email protected]>
30
 */
31
class StructureGenerator
32
{
33
34
    /**
35
     * @var SerializerInterface
36
     */
37
    private $serializer = null;
38
39
    /**
40
     * @var Parser
41
     */
42
    private $parser = null;
43
44
    /**
45
     * CONSTR
46
     *
47
     * @param Parser $parser
48
     */
49
    public function __construct(Parser $parser)
50
    {
51
        $this->parser = $parser;
52
        $this->serializer = SerializerBuilder::create()->build();
53
    }
54
55
    /**
56
     * Parse yaml string to array of JSONs
57
     *
58
     * @param string $fileContent
59
     * @return ArrayCollection
60
     */
61
    public function parseToArray($fileContent)
62
    {
63
        $parser = $this->getParser();
64
        $entitiesDataArray = $parser->parse($fileContent);
65
        $entitiesData = new ArrayCollection();
66
        foreach ($entitiesDataArray as $entityDataArray) {
0 ignored issues
show
Bug introduced by
The expression $entitiesDataArray of type string|array|object<stdClass> 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...
67
            $entitiesData->add(json_encode($entityDataArray, true));
68
        }
69
70
        return $entitiesData;
71
    }
72
73
    /**
74
     * Build Entities Class Structures from array array('{"data":"data"}', '{"data":"data"}')
75
     *
76
     * @param ArrayCollection $entitiesData
77
     * @param ClassConfig $classConfig
78
     * @return ArrayCollection
79
     */
80
    public function buildEntitiesClassStructure(ArrayCollection $entitiesData, ClassConfig $classConfig = null)
81
    {
82
        $classConfig = $this->getDefaultClassConfigIfNeed($classConfig);
83
        $classesManagers = new ArrayCollection();
84
        foreach ($entitiesData as $jsonData) {
85
            $classesManagers->add($this->deserializeJsonDataToClassManager($jsonData));
86
        }
87
88
        // building class environment
89
        foreach ($classesManagers as $classManager) {
90
            $this->prepareClassManager($classManager, $classConfig); // reference
91
        }
92
93
        return $classesManagers;
94
    }
95
96
    /**
97
     * Generate class components
98
     *
99
     * @param ClassManager $classManager
100
     * @param ClassConfig $classConfig
101
     * @return ClassManager
102
     */
103
    public function prepareClassManager(ClassManager $classManager, ClassConfig $classConfig = null)
104
    {
105
        $inClassConfiguration = $classManager->getConfiguration();
106
        $defaultClassConfiguration = $this->getDefaultClassConfigIfNeed($classConfig);
107
108
        $constructor = new ClassConstructorManager($classManager);
109
        $this->generateAndFillClassMethods($classManager);
110
        $this->prepareAndFillInitProperties($constructor);
111
        $classManager->setConstructor($constructor);
112
113
        // inline config is more important
114
        if ($inClassConfiguration instanceof ClassConfig) {
115
            $canAddInterface = !$inClassConfiguration->isNoInterface();
116
            $canPHPUnitClass = !$inClassConfiguration->isNoPHPUnitClass();
117
        } else {
118
            $canAddInterface = !$defaultClassConfiguration->isNoInterface();
119
            $canPHPUnitClass = !$defaultClassConfiguration->isNoPHPUnitClass();
120
        }
121
122
        if ($canAddInterface) {
123
            $interface = new InterfaceManager($classManager);
124
            $this->generateAndFillInterfaceMethods($interface);
125
            $classManager->setInterface($interface);
126
        }
127
        if ($canPHPUnitClass) {
128
            $testClass = new TestClassManager($classManager);
129
            $this->generateAndFillTestClassMethods($testClass);
130
            $classManager->setTestClass($testClass);
131
        }
132
133
        return $classManager;
134
    }
135
136
    /**
137
     * Generate class components
138
     * - setters and getters for Class and Interface (optional)
139
     * - method with prefix is for boolean properties
140
     *
141
     * @param ClassManager $classManager
142
     * @return ClassManager
143
     */
144
    protected function generateAndFillClassMethods(ClassManager $classManager)
145
    {
146
        $methodsForClass = new ArrayCollection();
147
148
        // fix - jms serializer does not call ClassManager constructor during deserialization
149
        if (false === ($classManager->getProperties() instanceof ArrayCollection)) {
150
            $classManager->setProperties(new ArrayCollection());
151
        }
152
153
        $this->appendPropertiesMethods($classManager, $methodsForClass);
154
        $this->appendMethodsDerivedFromInterface($classManager, $methodsForClass);
155
156
        $classManager->setMethods($methodsForClass);
157
158
        return $classManager;
159
    }
160
161
    protected function appendPropertiesMethods(ClassManager $classManager, ArrayCollection $appendTo)
162
    {
163 View Code Duplication
        foreach ($classManager->getProperties() as $property) {
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...
164
            if ($property->isTypeBoolean()) {
165
                $appendTo->add((new MethodGetterBooleanManager($classManager))->setProperty($property));
166
            }
167
168
            $methodSetterManager = new MethodSetterManager($classManager);
169
            $methodSetterManager->setProperty($property);
170
            $methodGetterManager = new MethodGetterManager($classManager);
171
            $methodGetterManager->setProperty($property);
172
173
            $appendTo->add($methodSetterManager);
174
            $appendTo->add($methodGetterManager);
175
        }
176
    }
177
178
    protected function appendMethodsDerivedFromInterface(ClassManager $classManager, ArrayCollection $appendTo)
179
    {
180
        $methodsToSkip = [];
181
        foreach($appendTo as $method) {
182
            $methodsToSkip[] = $method->getPreparedName();
183
        }
184
        foreach ($classManager->getImplements() as $namespace) {
185
            $builder = new DerivedMethodsBuilder($namespace, $classManager);
186
            $builder->setMethodsToSkip($methodsToSkip);
187
            foreach($builder->getMethodsDerivedFromInterface() as $derivedMethodManager) {
188
                $appendTo->add($derivedMethodManager);
189
            }
190
        }
191
    }
192
193
    /**
194
     * Generate methods for interface
195
     *
196
     * @param InterfaceManager $interface
197
     * @return ArrayCollection
198
     */
199
    protected function generateAndFillInterfaceMethods(InterfaceManager $interface)
200
    {
201
        $methodsForInterface = new ArrayCollection();
202
        $classManager = $interface->getClassManager();
203 View Code Duplication
        foreach ($classManager->getProperties() as $property) {
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...
204
            if ($property->isTypeBoolean()) {
205
                $methodsForInterface->add((new MethodGetterBooleanInterfaceManager($classManager))->setProperty($property));
206
            }
207
208
            $methodSetterInterfaceManager = new MethodSetterInterfaceManager($classManager);
209
            $methodSetterInterfaceManager->setProperty($property);
210
            $methodGetterInterfaceManager = new MethodGetterInterfaceManager($classManager);
211
            $methodGetterInterfaceManager->setProperty($property);
212
213
            $methodsForInterface->add($methodSetterInterfaceManager);
214
            $methodsForInterface->add($methodGetterInterfaceManager);
215
        }
216
217
        $interface->setMethods($methodsForInterface);
218
219
        return $interface;
220
    }
221
222
    /**
223
     * Prepare list of init properties for constructor
224
     *
225
     * @param ClassConstructorManager $classConstructor
226
     * @return ClassConstructorManager
227
     */
228
    protected function prepareAndFillInitProperties(ClassConstructorManager $classConstructor)
229
    {
230
        $initProperties = new ArrayCollection();
231
        foreach ($classConstructor->getClassManager()->getProperties() as $property) {
232
            if (false === $property->isTypeArrayCollection()) {
233
                continue;
234
            }
235
236
            $initProperty = new InitPropertyManager();
237
            $initProperty->setProperty($property);
238
239
            $initProperties->add($initProperty);
240
        }
241
242
        $classConstructor->setInitProperties($initProperties);
243
244
        return $classConstructor;
245
    }
246
247
    /**
248
     * init test class for entity
249
     *
250
     * @param TestClassManager $testClassManager
251
     * @return TestClassManager
252
     */
253
    protected function generateAndFillTestClassMethods(TestClassManager $testClassManager)
254
    {
255
        $testMethods = new ArrayCollection();
256
        foreach ($testClassManager->getClassManager()->getMethods() as $method) {
257
            $this->buildAndAppendTestMethodIfNecessary($method, $testMethods);
258
        }
259
260
        $testClassManager->setMethods($testMethods);
261
262
        return $testClassManager;
263
    }
264
265
    protected function buildAndAppendTestMethodIfNecessary(MethodManager $method, ArrayCollection $appendTo)
266
    {
267
        if ($method instanceof MethodForPropertyManager) {
268
            $testMethod = new TestMethodManager();
269
            $testMethod->setMethod($method);
270
            $appendTo->add($testMethod);
271
        }
272
    }
273
274
    /**
275
     * Deserialize JSON data to class manager
276
     *
277
     * @param string $jsonClassToDeserialize
278
     * @return ClassManager
279
     */
280
    protected function deserializeJsonDataToClassManager($jsonClassToDeserialize)
281
    {
282
        return $this->getSerializer()->deserialize($jsonClassToDeserialize, ClassManager::class, 'json');
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->getSeriali...anager::class, 'json'); (object|array|integer|double|string|boolean) is incompatible with the return type documented by SimpleEntityGeneratorBun...eJsonDataToClassManager of type SimpleEntityGeneratorBundle\Lib\Items\ClassManager.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
283
    }
284
285
    /**
286
     * @return SerializerInterface
287
     */
288
    protected function getSerializer()
289
    {
290
        return $this->serializer;
291
    }
292
293
    /**
294
     * @return Parser
295
     */
296
    protected function getParser()
297
    {
298
        return $this->parser;
299
    }
300
301
    /**
302
     * @param mixed $classConfig
303
     * @return ClassConfig
304
     */
305
    private function getDefaultClassConfigIfNeed($classConfig)
306
    {
307
        if (false === ($classConfig instanceof ClassConfig)) {
308
            $classConfig = new ClassConfig();
309
        }
310
311
        return $classConfig;
312
    }
313
}
314