Completed
Push — master ( deb735...54123f )
by Marco
11s
created

HydratorFunctionalTest::testHydratingNull()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 10
rs 9.4285
cc 1
eloc 5
nc 1
nop 0
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
    public function testHydratingNull()
88
    {
89
        $instance = new ClassWithPrivateProperties();
90
91
        self::assertSame('property0', $instance->getProperty0());
92
93
        $this->generateHydrator($instance)->hydrate(['property0' => null], $instance);
94
95
        self::assertSame(null, $instance->getProperty0());
96
    }
97
98
    /**
99
     * @return array
100
     */
101
    public function getHydratorClasses() : array
102
    {
103
        return [
104
            [new stdClass()],
105
            [new EmptyClass()],
106
            [new HydratedObject()],
107
            [new BaseClass()],
108
            [new ClassWithPublicProperties()],
109
            [new ClassWithProtectedProperties()],
110
            [new ClassWithPrivateProperties()],
111
            [new ClassWithPrivatePropertiesAndParent()],
112
            [new ClassWithPrivatePropertiesAndParents()],
113
            [new ClassWithMixedProperties()],
114
            [new ClassWithStaticProperties()],
115
        ];
116
    }
117
118
    /**
119
     * Recursively populate the $initialData and $newData array browsing the
120
     * full class hierarchy tree
121
     *
122
     * Private properties from parent class that are hidden by children will be
123
     * dropped from the populated arrays
124
     *
125
     * @param \ReflectionClass $class
126
     * @param object $instance
127
     * @param array $initialData
128
     * @param array $newData
129
     */
130
    private function recursiveFindInitialData(\ReflectionClass $class, $instance, array &$initialData, array &$newData)
131
    {
132
        if ($parentClass = $class->getParentClass()) {
133
            $this->recursiveFindInitialData($parentClass, $instance, $initialData, $newData);
134
        }
135
136
        foreach ($class->getProperties() as $property) {
137
            if ($property->isStatic()) {
138
                continue;
139
            }
140
141
            $propertyName = $property->getName();
142
143
            $property->setAccessible(true);
144
            $initialData[$propertyName] = $property->getValue($instance);
145
            $newData[$propertyName]     = $property->getName() . '__new__value';
146
        }
147
    }
148
149
    /**
150
     * Recursively populate the $inspectedData array browsing the full class
151
     * hierarchy tree
152
     *
153
     * Private properties from parent class that are hidden by children will be
154
     * dropped from the populated arrays
155
     *
156
     * @param \ReflectionClass $class
157
     * @param object $instance
158
     * @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...
159
     * @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...
160
     */
161
    private function recursiveFindInspectionData(\ReflectionClass $class, $instance, array &$inspectionData)
162
    {
163
        if ($parentClass = $class->getParentClass()) {
164
            $this->recursiveFindInspectionData($parentClass, $instance, $inspectionData);
165
        }
166
167
        foreach ($class->getProperties() as $property) {
168
            if ($property->isStatic()) {
169
                continue;
170
            }
171
172
            $propertyName = $property->getName();
173
174
            $property->setAccessible(true);
175
            $inspectionData[$propertyName] = $property->getValue($instance);
176
        }
177
    }
178
179
    /**
180
     * Generates a hydrator for the given class name, and retrieves its class name
181
     *
182
     * @param object $instance
183
     *
184
     * @return HydratorInterface
185
     */
186
    private function generateHydrator($instance) : HydratorInterface
187
    {
188
        $parentClassName    = get_class($instance);
189
        $generatedClassName = __NAMESPACE__ . '\\' . UniqueIdentifierGenerator::getIdentifier('Foo');
190
        $config             = new Configuration($parentClassName);
191
        /* @var $inflector ClassNameInflectorInterface|\PHPUnit_Framework_MockObject_MockObject */
192
        $inflector          = $this->createMock(ClassNameInflectorInterface::class);
193
194
        $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...
195
            ->expects(self::any())
196
            ->method('getGeneratedClassName')
197
            ->with($parentClassName)
198
            ->will(self::returnValue($generatedClassName));
199
        $inflector
200
            ->expects(self::any())
201
            ->method('getUserClassName')
202
            ->with($parentClassName)
203
            ->will(self::returnValue($parentClassName));
204
205
        $config->setClassNameInflector($inflector);
0 ignored issues
show
Bug introduced by
It seems like $inflector defined by $this->createMock(\CodeG...lectorInterface::class) on line 192 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...
206
        $config->setGeneratorStrategy(new EvaluatingGeneratorStrategy());
207
208
        $generatedClass = $config->createFactory()->getHydratorClass();
209
210
        return new $generatedClass;
211
    }
212
}
213