Completed
Pull Request — master (#59)
by
unknown
05:37 queued 02:32
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
        self::assertSame($initialData, $generatedClass->extract($instance));
68
        self::assertSame($instance, $generatedClass->hydrate($newData, $instance));
69
70
        $inspectionData = array();
71
        $this->recursiveFindInspectionData($reflection, $instance, $inspectionData);
72
73
        self::assertSame($inspectionData, $newData);
74
        self::assertSame($inspectionData, $generatedClass->extract($instance));
75
    }
76
77
    /**
78
     * @return array
79
     */
80
    public function getHydratorClasses() : array
81
    {
82
        return [
83
            [new stdClass()],
84
            [new EmptyClass()],
85
            [new HydratedObject()],
86
            [new BaseClass()],
87
            [new ClassWithPublicProperties()],
88
            [new ClassWithProtectedProperties()],
89
            [new ClassWithPrivateProperties()],
90
            [new ClassWithPrivatePropertiesAndParent()],
91
            [new ClassWithPrivatePropertiesAndParents()],
92
            [new ClassWithMixedProperties()],
93
            [new ClassWithStaticProperties()],
94
        ];
95
    }
96
97
    /**
98
     * Recursively populate the $initialData and $newData array browsing the
99
     * full class hierarchy tree
100
     *
101
     * Private properties from parent class that are hidden by children will be
102
     * dropped from the populated arrays
103
     *
104
     * @param \ReflectionClass $class
105
     * @param object $instance
106
     * @param array $initialData
107
     * @param array $newData
108
     */
109
    private function recursiveFindInitialData(\ReflectionClass $class, $instance, array &$initialData, array &$newData)
110
    {
111
        if ($parentClass = $class->getParentClass()) {
112
            $this->recursiveFindInitialData($parentClass, $instance, $initialData, $newData);
113
        }
114
115
        foreach ($class->getProperties() as $property) {
116
            if ($property->isStatic()) {
117
                continue;
118
            }
119
120
            $propertyName = $property->getName();
121
122
            $property->setAccessible(true);
123
            $initialData[$propertyName] = $property->getValue($instance);
124
            $newData[$propertyName]     = $property->getName() . '__new__value';
125
        }
126
    }
127
128
    /**
129
     * Recursively populate the $inspectedData array browsing the full class
130
     * hierarchy tree
131
     *
132
     * Private properties from parent class that are hidden by children will be
133
     * dropped from the populated arrays
134
     *
135
     * @param \ReflectionClass $class
136
     * @param object $instance
137
     * @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...
138
     * @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...
139
     */
140
    private function recursiveFindInspectionData(\ReflectionClass $class, $instance, array &$inspectionData)
141
    {
142
        if ($parentClass = $class->getParentClass()) {
143
            $this->recursiveFindInspectionData($parentClass, $instance, $inspectionData);
144
        }
145
146
        foreach ($class->getProperties() as $property) {
147
            if ($property->isStatic()) {
148
                continue;
149
            }
150
151
            $propertyName = $property->getName();
152
153
            $property->setAccessible(true);
154
            $inspectionData[$propertyName] = $property->getValue($instance);
155
        }
156
    }
157
158
    /**
159
     * Generates a hydrator for the given class name, and retrieves its class name
160
     *
161
     * @param object $instance
162
     *
163
     * @return HydratorInterface
164
     */
165
    private function generateHydrator($instance) : HydratorInterface
166
    {
167
        $parentClassName    = get_class($instance);
168
        $generatedClassName = __NAMESPACE__ . '\\' . UniqueIdentifierGenerator::getIdentifier('Foo');
169
        $config             = new Configuration($parentClassName);
170
        /* @var $inflector ClassNameInflectorInterface|\PHPUnit_Framework_MockObject_MockObject */
171
        $inflector          = $this->createMock(ClassNameInflectorInterface::class);
172
173
        $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...
174
            ->expects(self::any())
175
            ->method('getGeneratedClassName')
176
            ->with($parentClassName)
177
            ->will(self::returnValue($generatedClassName));
178
        $inflector
179
            ->expects(self::any())
180
            ->method('getUserClassName')
181
            ->with($parentClassName)
182
            ->will(self::returnValue($parentClassName));
183
184
        $config->setClassNameInflector($inflector);
0 ignored issues
show
Bug introduced by
It seems like $inflector defined by $this->createMock(\CodeG...lectorInterface::class) on line 171 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...
185
        $config->setGeneratorStrategy(new EvaluatingGeneratorStrategy());
186
187
        $generatedClass = $config->createFactory()->getHydratorClass();
188
189
        return new $generatedClass;
190
    }
191
}
192