Completed
Pull Request — master (#59)
by
unknown
05:55
created

HydratorFunctionalTest::generateHydrator()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 26
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 26
rs 8.8571
c 0
b 0
f 0
cc 1
eloc 19
nc 1
nop 1
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
41
/**
42
 * Tests for {@see \GeneratedHydrator\ClassGenerator\HydratorGenerator} produced objects
43
 *
44
 * @author Marco Pivetta <[email protected]>
45
 * @license MIT
46
 *
47
 * @group Functional
48
 */
49
class HydratorFunctionalTest extends PHPUnit_Framework_TestCase
50
{
51
    /**
52
     * @dataProvider getHydratorClasses
53
     *
54
     * @param object $instance
55
     */
56
    public function testHydrator($instance)
57
    {
58
        $reflection  = new ReflectionClass($instance);
59
        $initialData = array();
60
        $newData     = array();
61
62
        $this->recursiveFindInitialData($reflection, $instance, $initialData, $newData);
63
64
        $generatedClass = $this->generateHydrator($instance);
65
66
        self::assertSame($initialData, $generatedClass->extract($instance));
67
        self::assertSame($instance, $generatedClass->hydrate($newData, $instance));
68
69
        $inspectionData = array();
70
        $this->recursiveFindInspectionData($reflection, $instance, $inspectionData);
71
72
        self::assertSame($inspectionData, $newData);
73
        self::assertSame($inspectionData, $generatedClass->extract($instance));
74
    }
75
76
    /**
77
     * @return array
78
     */
79
    public function getHydratorClasses() : array
80
    {
81
        return [
82
            [new stdClass()],
83
            [new EmptyClass()],
84
            [new HydratedObject()],
85
            [new BaseClass()],
86
            [new ClassWithPublicProperties()],
87
            [new ClassWithProtectedProperties()],
88
            [new ClassWithPrivateProperties()],
89
            [new ClassWithPrivatePropertiesAndParent()],
90
            [new ClassWithMixedProperties()],
91
            [new ClassWithStaticProperties()],
92
        ];
93
    }
94
95
    /**
96
     * Recursively populate the $initialData and $newData array browsing the
97
     * full class hierarchy tree
98
     *
99
     * Private properties from parent class that are hidden by children will be
100
     * dropped from the populated arrays
101
     *
102
     * @param \ReflectionClass $class
103
     * @param object $instance
104
     * @param array $initialData
105
     * @param array $newData
106
     */
107
    private function recursiveFindInitialData(\ReflectionClass $class, $instance, array &$initialData, array &$newData)
108
    {
109
        if ($parentClass = $class->getParentClass()) {
110
            $this->recursiveFindInitialData($parentClass, $instance, $initialData, $newData);
111
        }
112
113
        foreach ($class->getProperties() as $property) {
114
            if ($property->isStatic()) {
115
                continue;
116
            }
117
118
            $propertyName = $property->getName();
119
120
            $property->setAccessible(true);
121
            $initialData[$propertyName] = $property->getValue($instance);
122
            $newData[$propertyName]     = $property->getName() . '__new__value';
123
        }
124
    }
125
126
    /**
127
     * Recursively populate the $inspectedData array browsing the full class
128
     * hierarchy tree
129
     *
130
     * Private properties from parent class that are hidden by children will be
131
     * dropped from the populated arrays
132
     *
133
     * @param \ReflectionClass $class
134
     * @param object $instance
135
     * @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...
136
     * @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...
137
     */
138
    private function recursiveFindInspectionData(\ReflectionClass $class, $instance, array &$inspectionData)
139
    {
140
        if ($parentClass = $class->getParentClass()) {
141
            $this->recursiveFindInspectionData($parentClass, $instance, $inspectionData);
142
        }
143
144
        foreach ($class->getProperties() as $property) {
145
            if ($property->isStatic()) {
146
                continue;
147
            }
148
149
            $propertyName = $property->getName();
150
151
            $property->setAccessible(true);
152
            $inspectionData[$propertyName] = $property->getValue($instance);
153
        }
154
    }
155
156
    /**
157
     * Generates a hydrator for the given class name, and retrieves its class name
158
     *
159
     * @param object $instance
160
     *
161
     * @return HydratorInterface
162
     */
163
    private function generateHydrator($instance) : HydratorInterface
164
    {
165
        $parentClassName    = get_class($instance);
166
        $generatedClassName = __NAMESPACE__ . '\\' . UniqueIdentifierGenerator::getIdentifier('Foo');
167
        $config             = new Configuration($parentClassName);
168
        /* @var $inflector ClassNameInflectorInterface|\PHPUnit_Framework_MockObject_MockObject */
169
        $inflector          = $this->createMock(ClassNameInflectorInterface::class);
170
171
        $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...
172
            ->expects(self::any())
173
            ->method('getGeneratedClassName')
174
            ->with($parentClassName)
175
            ->will(self::returnValue($generatedClassName));
176
        $inflector
177
            ->expects(self::any())
178
            ->method('getUserClassName')
179
            ->with($parentClassName)
180
            ->will(self::returnValue($parentClassName));
181
182
        $config->setClassNameInflector($inflector);
0 ignored issues
show
Bug introduced by
It seems like $inflector defined by $this->createMock(\CodeG...lectorInterface::class) on line 169 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...
183
        $config->setGeneratorStrategy(new EvaluatingGeneratorStrategy());
184
185
        $generatedClass = $config->createFactory()->getHydratorClass();
186
187
        return new $generatedClass;
188
    }
189
}
190