Completed
Push — master ( 725e76...e58edb )
by Marco
04:59
created

HydratorFunctionalTest::recursiveFindInitialData()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 18
rs 9.2
cc 4
eloc 10
nc 6
nop 4
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license.
17
 */
18
19
declare(strict_types=1);
20
21
namespace GeneratedHydratorTest\Functional;
22
23
use CodeGenerationUtils\GeneratorStrategy\EvaluatingGeneratorStrategy;
24
use CodeGenerationUtils\Inflector\ClassNameInflectorInterface;
25
use CodeGenerationUtils\Inflector\Util\UniqueIdentifierGenerator;
26
use GeneratedHydrator\Configuration;
27
use GeneratedHydratorTestAsset\BaseClass;
28
use GeneratedHydratorTestAsset\ClassWithMixedProperties;
29
use GeneratedHydratorTestAsset\ClassWithPrivateProperties;
30
use GeneratedHydratorTestAsset\ClassWithPrivatePropertiesAndParent;
31
use GeneratedHydratorTestAsset\ClassWithProtectedProperties;
32
use GeneratedHydratorTestAsset\ClassWithPublicProperties;
33
use GeneratedHydratorTestAsset\ClassWithStaticProperties;
34
use GeneratedHydratorTestAsset\EmptyClass;
35
use GeneratedHydratorTestAsset\HydratedObject;
36
use PHPUnit_Framework_TestCase;
37
use ReflectionClass;
38
use stdClass;
39
use Zend\Hydrator\HydratorInterface;
40
use GeneratedHydratorTestAsset\ClassWithPrivatePropertiesAndParents;
41
42
/**
43
 * Tests for {@see \GeneratedHydrator\ClassGenerator\HydratorGenerator} produced objects
44
 *
45
 * @author Marco Pivetta <[email protected]>
46
 * @license MIT
47
 *
48
 * @group Functional
49
 */
50
class HydratorFunctionalTest extends PHPUnit_Framework_TestCase
51
{
52
    /**
53
     * @dataProvider getHydratorClasses
54
     *
55
     * @param object $instance
56
     */
57
    public function testHydrator($instance)
58
    {
59
        $reflection  = new ReflectionClass($instance);
60
        $initialData = array();
61
        $newData     = array();
62
63
        $this->recursiveFindInitialData($reflection, $instance, $initialData, $newData);
64
65
        $generatedClass = $this->generateHydrator($instance);
66
67
        // Hydration and extraction don't guarantee ordering.
68
        ksort($initialData);
69
        ksort($newData);
70
        $extracted = $generatedClass->extract($instance);
71
        ksort($extracted);
72
73
        self::assertSame($initialData, $extracted);
74
        self::assertSame($instance, $generatedClass->hydrate($newData, $instance));
75
76
        // Same as upper applies
77
        $inspectionData = array();
78
        $this->recursiveFindInspectionData($reflection, $instance, $inspectionData);
79
        ksort($inspectionData);
80
        $extracted = $generatedClass->extract($instance);
81
        ksort($extracted);
82
83
        self::assertSame($inspectionData, $newData);
84
        self::assertSame($inspectionData, $extracted);
85
    }
86
87
    /**
88
     * @return array
89
     */
90
    public function getHydratorClasses() : array
91
    {
92
        return [
93
            [new stdClass()],
94
            [new EmptyClass()],
95
            [new HydratedObject()],
96
            [new BaseClass()],
97
            [new ClassWithPublicProperties()],
98
            [new ClassWithProtectedProperties()],
99
            [new ClassWithPrivateProperties()],
100
            [new ClassWithPrivatePropertiesAndParent()],
101
            [new ClassWithPrivatePropertiesAndParents()],
102
            [new ClassWithMixedProperties()],
103
            [new ClassWithStaticProperties()],
104
        ];
105
    }
106
107
    /**
108
     * Recursively populate the $initialData and $newData array browsing the
109
     * full class hierarchy tree
110
     *
111
     * Private properties from parent class that are hidden by children will be
112
     * dropped from the populated arrays
113
     *
114
     * @param \ReflectionClass $class
115
     * @param object $instance
116
     * @param array $initialData
117
     * @param array $newData
118
     */
119
    private function recursiveFindInitialData(\ReflectionClass $class, $instance, array &$initialData, array &$newData)
120
    {
121
        if ($parentClass = $class->getParentClass()) {
122
            $this->recursiveFindInitialData($parentClass, $instance, $initialData, $newData);
123
        }
124
125
        foreach ($class->getProperties() as $property) {
126
            if ($property->isStatic()) {
127
                continue;
128
            }
129
130
            $propertyName = $property->getName();
131
132
            $property->setAccessible(true);
133
            $initialData[$propertyName] = $property->getValue($instance);
134
            $newData[$propertyName]     = $property->getName() . '__new__value';
135
        }
136
    }
137
138
    /**
139
     * Recursively populate the $inspectedData array browsing the full class
140
     * hierarchy tree
141
     *
142
     * Private properties from parent class that are hidden by children will be
143
     * dropped from the populated arrays
144
     *
145
     * @param \ReflectionClass $class
146
     * @param object $instance
147
     * @param array $initialData
0 ignored issues
show
Bug introduced by
There is no parameter named $initialData. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
148
     * @param array $newData
0 ignored issues
show
Bug introduced by
There is no parameter named $newData. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
149
     */
150
    private function recursiveFindInspectionData(\ReflectionClass $class, $instance, array &$inspectionData)
151
    {
152
        if ($parentClass = $class->getParentClass()) {
153
            $this->recursiveFindInspectionData($parentClass, $instance, $inspectionData);
154
        }
155
156
        foreach ($class->getProperties() as $property) {
157
            if ($property->isStatic()) {
158
                continue;
159
            }
160
161
            $propertyName = $property->getName();
162
163
            $property->setAccessible(true);
164
            $inspectionData[$propertyName] = $property->getValue($instance);
165
        }
166
    }
167
168
    /**
169
     * Generates a hydrator for the given class name, and retrieves its class name
170
     *
171
     * @param object $instance
172
     *
173
     * @return HydratorInterface
174
     */
175
    private function generateHydrator($instance) : HydratorInterface
176
    {
177
        $parentClassName    = get_class($instance);
178
        $generatedClassName = __NAMESPACE__ . '\\' . UniqueIdentifierGenerator::getIdentifier('Foo');
179
        $config             = new Configuration($parentClassName);
180
        /* @var $inflector ClassNameInflectorInterface|\PHPUnit_Framework_MockObject_MockObject */
181
        $inflector          = $this->createMock(ClassNameInflectorInterface::class);
182
183
        $inflector
0 ignored issues
show
Bug introduced by
The method expects does only exist in PHPUnit_Framework_MockObject_MockObject, but not in CodeGenerationUtils\Infl...sNameInflectorInterface.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
184
            ->expects(self::any())
185
            ->method('getGeneratedClassName')
186
            ->with($parentClassName)
187
            ->will(self::returnValue($generatedClassName));
188
        $inflector
189
            ->expects(self::any())
190
            ->method('getUserClassName')
191
            ->with($parentClassName)
192
            ->will(self::returnValue($parentClassName));
193
194
        $config->setClassNameInflector($inflector);
0 ignored issues
show
Bug introduced by
It seems like $inflector defined by $this->createMock(\CodeG...lectorInterface::class) on line 181 can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, GeneratedHydrator\Config...setClassNameInflector() does only seem to accept object<CodeGenerationUti...NameInflectorInterface>, 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...
195
        $config->setGeneratorStrategy(new EvaluatingGeneratorStrategy());
196
197
        $generatedClass = $config->createFactory()->getHydratorClass();
198
199
        return new $generatedClass;
200
    }
201
}
202