Completed
Push — master ( e32eeb...714cda )
by
unknown
89:50 queued 46:29
created

EntityTestGenerator::getProperties()   D

Complexity

Conditions 9
Paths 5

Size

Total Lines 30
Code Lines 23

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 30
rs 4.909
cc 9
eloc 23
nc 5
nop 1
1
<?php
2
3
namespace Oro\Bundle\TestGeneratorBundle\Generator;
4
5
class EntityTestGenerator extends AbstractTestGenerator
6
{
7
    /**
8
     * @var string
9
     */
10
    protected $className;
11
12
    /**
13
     * @param string $className
14
     */
15 View Code Duplication
    public function generate($className)
16
    {
17
        $this->className = $className;
18
        $class = new \ReflectionClass($className);
19
        $propertiesData = $this->getProperties($class);
20
        $fullTestNameSpace = $this->getNamespaceForTest($className, 'unit');
21
        $parts = explode('\\', $fullTestNameSpace);
22
        $testClassName = array_pop($parts);
23
        $partsOfOriginClass = explode('\\', $className);
24
        $testedClassName = array_pop($partsOfOriginClass);
25
        $nameSpace = implode('\\', $parts);
26
        $testPath = $this->getTestPath($fullTestNameSpace);
27
        $constructor = $class->getConstructor();
28
        $dependencies = $constructor ? $this->getDependencies($constructor) : [];
29
        $dependenciesData = $this->getDependenciesData($dependencies);
30
        $this->addClassToUses($className);
31
        $this->addClassToUses('Oro\Component\Testing\Unit\EntityTestCaseTrait');
32
        $orderedUses = $this->getOrderedUses($this->usedClasses);
33
        $content = $this->twig->render(
34
            '@OroTestGenerator/Tests/entity_template.php.twig',
35
            [
36
                'namespace' => $nameSpace,
37
                'vendors' => $orderedUses,
38
                'className' => $testClassName,
39
                'testedClassName' => $testedClassName,
40
                'testedClassNameVariable' => lcfirst($testedClassName),
41
                'dependenciesData' => $dependenciesData,
42
                'propertiesData' => $propertiesData
43
            ]
44
        );
45
        $this->createFile($testPath, $content);
46
    }
47
48
    /**
49
     * @param \ReflectionClass $class
50
     * @return array
51
     */
52
    protected function getProperties(\ReflectionClass $class)
53
    {
54
        $data = [];
55
        $uses = $this->getUses($class);
56
        foreach ($class->getProperties() as $property) {
57
            $type = $this->getPropertyType($property);
58
            $temp = [];
59
            $temp['propertyName'] = $property->getName();
60
            if ($type) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $type of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
61
                if ($type === 'integer' || $type === 'float'
62
                    || $type === 'string' || $type === 'bool' || $type === 'boolean'
63
                ) {
64
                    $temp['type'] = $type;
65
                    $temp = $this->fillScalarType($type, $temp);
66
                } elseif (strpos($type, '[]') !== false) {
67
                    $temp['type'] = str_replace('[]', '', $type);
68
                    $temp['fullClass'] = $this->getFullClassName($temp['type'], $uses);
69
                    $temp['collection'] = true;
70
                    $this->addClassToUses($temp['fullClass']);
71
                } else {
72
                    $temp['type'] = $type;
73
                    $temp['fullClass'] = $this->getFullClassName($type, $uses);
74
                    $this->addClassToUses($temp['fullClass']);
0 ignored issues
show
Bug introduced by
It seems like $temp['fullClass'] can also be of type boolean; however, Oro\Bundle\TestGenerator...rator::addClassToUses() does only seem to accept object<ReflectionClass>|string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
75
                }
76
                $data[] = $temp;
77
            }
78
        }
79
80
        return $this->groupPropertiesByCollection($data);
81
    }
82
83
    /**
84
     * @param array $properties
85
     * @return array
86
     */
87
    protected function groupPropertiesByCollection($properties)
88
    {
89
        $result['collection'] = [];
0 ignored issues
show
Coding Style Comprehensibility introduced by
$result was never initialized. Although not strictly required by PHP, it is generally a good practice to add $result = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
90
        $result['simple'] = [];
91
        foreach ($properties as $property) {
92
            if (isset($property['collection'])) {
93
                unset($property['collection']);
94
                $result['collection'][] = $property;
95
            } else {
96
                $result['simple'][] = $property;
97
            }
98
        }
99
100
        return $result;
101
    }
102
103
    /**
104
     * @param \ReflectionProperty $property
105
     * @return string|false
106
     */
107
    protected function getPropertyType(\ReflectionProperty $property)
108
    {
109
        $doc = $property->getDocComment();
110
        preg_match_all('#@(.*?)\n#s', $doc, $annotations);
111
        $annotations = $annotations[1];
112
        $resultAnnotation = null;
113
        foreach ($annotations as $annotation) {
114
            if (strpos($annotation, 'var ') !== false) {
115
                $annotation = str_replace('var ', '', $annotation);
116
                if (strpos($annotation, '$') !== false) {
117
                    $annotation = explode(' ', $annotation)[0];
118
                }
119
                $resultAnnotation = $annotation;
120
                break;
121
            }
122
        }
123
        if (!$resultAnnotation) {
124
            return false;
125
        }
126
        if (strpos($resultAnnotation, '|') === false) {
127
            return $resultAnnotation;
128
        } else {
129
            $parts = explode('|', $resultAnnotation);
130
            foreach ($parts as $part) {
131
                if (strpos($part, '[]') !== false) {
132
                    return $part;
133
                }
134
            }
135
        }
136
137
        return false;
138
    }
139
140
    /**
141
     * @param \ReflectionClass $class
142
     * @return array
143
     */
144
    protected function getUses(\ReflectionClass $class)
145
    {
146
        $result = [];
147
        $lines = file($class->getFileName());
148
        $i = 4;
149
        while (strpos($lines[$i], '/*') === false && strpos($lines[$i], 'class ') === false) {
150
            if ($lines[$i] !== "\n" && strpos($lines[$i], ' as ') === false) {
151
                $result[] = str_replace(['use ', ';' . PHP_EOL], '', $lines[$i]);
152
            }
153
            $i++;
154
        }
155
156
        return $result;
157
    }
158
159
    /**
160
     * @param $className
161
     * @param string[] $fullClassNames
162
     * @return bool|string
163
     */
164
    protected function getFullClassName($className, $fullClassNames)
165
    {
166
        if (strpos($className, '\\') === 0) {
167
            return $className;
168
        }
169
170
        foreach ($fullClassNames as $fullClassName) {
171
            $parts = explode('\\', $fullClassName);
172
            if ($className === end($parts)) {
173
                return $fullClassName;
174
            }
175
        }
176
        $parts = explode('\\', $this->className);
177
        array_pop($parts);
178
179
        return implode('\\', $parts) . '\\' . $className;
180
    }
181
182
    /**
183
     * @param string $type
184
     * @param array $temp
185
     * @return array
186
     */
187
    protected function fillScalarType($type, $temp)
188
    {
189
        if ($type === 'integer') {
190
            $temp['value'] = 42;
191
192
            return $temp;
193
        } elseif ($type === 'float') {
194
            $temp['value'] = 3.1415926;
195
196
            return $temp;
197
        } elseif ($type === 'bool' || $type === 'boolean') {
198
            $temp['value'] = true;
199
200
            return $temp;
201
        } else {
202
            $temp['value'] = 'some string';
203
            $temp['quotes'] = true;
204
205
            return $temp;
206
        }
207
    }
208
}
209