Completed
Push — master ( 0675d2...e555ac )
by Jaap
01:36
created

testResolvingCollectionOfCollection()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 25
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 25
rs 8.8571
cc 1
eloc 16
nc 1
nop 0
1
<?php
2
/**
3
 * This file is part of phpDocumentor.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 *
8
 * @copyright 2010-2015 Mike van Riel<[email protected]>
9
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
10
 * @link      http://phpdoc.org
11
 */
12
13
namespace phpDocumentor\Reflection;
14
15
use phpDocumentor\Reflection\Types\Array_;
16
use phpDocumentor\Reflection\Types\Collection;
17
use phpDocumentor\Reflection\Types\Compound;
18
use phpDocumentor\Reflection\Types\Context;
19
use phpDocumentor\Reflection\Types\Object_;
20
21
/**
22
 * @covers ::<private>
23
 * @coversDefaultClass phpDocumentor\Reflection\TypeResolver
24
 */
25
class CollectionResolverTest extends \PHPUnit_Framework_TestCase
26
{
27
    /**
28
     *
29
     * @covers ::__construct
30
     * @covers ::resolve
31
     *
32
     * @uses \phpDocumentor\Reflection\Types\Context
33
     * @uses \phpDocumentor\Reflection\Types\Compound
34
     * @uses \phpDocumentor\Reflection\Types\Collection
35
     * @uses \phpDocumentor\Reflection\Types\String_
36
     */
37
    public function testResolvingCollection() {
38
        $fixture = new TypeResolver();
39
40
        /** @var Collection $resolvedType */
41
        $resolvedType = $fixture->resolve('ArrayObject<string>', new Context(''));
42
43
        $this->assertInstanceOf(Collection::class, $resolvedType);
44
        $this->assertSame('\\ArrayObject<string>', (string)$resolvedType);
45
46
        $this->assertEquals('\\ArrayObject', (string) $resolvedType->getFqsen());
47
48
        /** @var Array_ $valueType */
49
        $valueType = $resolvedType->getValueType();
50
51
        /** @var Compound $keyType */
52
        $keyType = $resolvedType->getKeyType();
53
54
        $this->assertInstanceOf(Types\String_::class, $valueType);
55
        $this->assertInstanceOf(Types\Compound::class, $keyType);
56
    }
57
58
    /**
59
     *
60
     * @covers ::__construct
61
     * @covers ::resolve
62
     *
63
     * @uses \phpDocumentor\Reflection\Types\Context
64
     * @uses \phpDocumentor\Reflection\Types\Compound
65
     * @uses \phpDocumentor\Reflection\Types\Collection
66
     * @uses \phpDocumentor\Reflection\Types\String_
67
     */
68
    public function testResolvingCollectionWithKeyType() {
69
        $fixture = new TypeResolver();
70
71
        /** @var Collection $resolvedType */
72
        $resolvedType = $fixture->resolve('ArrayObject<string[],Iterator>', new Context(''));
73
74
        $this->assertInstanceOf(Collection::class, $resolvedType);
75
        $this->assertSame('\\ArrayObject<string[],\\Iterator>', (string)$resolvedType);
76
77
        $this->assertEquals('\\ArrayObject', (string) $resolvedType->getFqsen());
78
79
        /** @var Object_ $valueType */
80
        $valueType = $resolvedType->getValueType();
81
82
        /** @var Array_ $keyType */
83
        $keyType = $resolvedType->getKeyType();
84
85
        $this->assertInstanceOf(Types\Object_::class, $valueType);
86
        $this->assertEquals('\\Iterator', (string) $valueType->getFqsen());
87
        $this->assertInstanceOf(Types\Array_::class, $keyType);
88
        $this->assertInstanceOf(Types\String_::class, $keyType->getValueType());
89
    }
90
91
    /**
92
     *
93
     * @covers ::__construct
94
     * @covers ::resolve
95
     *
96
     * @uses \phpDocumentor\Reflection\Types\Context
97
     * @uses \phpDocumentor\Reflection\Types\Compound
98
     * @uses \phpDocumentor\Reflection\Types\Collection
99
     * @uses \phpDocumentor\Reflection\Types\String_
100
     */
101
    public function testResolvingArrayCollection() {
102
        $fixture = new TypeResolver();
103
104
        /** @var Collection $resolvedType */
105
        $resolvedType = $fixture->resolve('array<string>', new Context(''));
106
107
        $this->assertInstanceOf(Array_::class, $resolvedType);
108
        $this->assertSame('string[]', (string)$resolvedType);
109
110
        /** @var Array_ $valueType */
111
        $valueType = $resolvedType->getValueType();
112
113
        /** @var Compound $keyType */
114
        $keyType = $resolvedType->getKeyType();
115
116
        $this->assertInstanceOf(Types\String_::class, $valueType);
117
        $this->assertInstanceOf(Types\Compound::class, $keyType);
118
    }
119
120
    /**
121
     *
122
     * @covers ::__construct
123
     * @covers ::resolve
124
     *
125
     * @uses \phpDocumentor\Reflection\Types\Context
126
     * @uses \phpDocumentor\Reflection\Types\Compound
127
     * @uses \phpDocumentor\Reflection\Types\Collection
128
     * @uses \phpDocumentor\Reflection\Types\String_
129
     */
130
    public function testResolvingArrayCollectionWithKey() {
131
        $fixture = new TypeResolver();
132
133
        /** @var Collection $resolvedType */
134
        $resolvedType = $fixture->resolve('array<string,object|array>', new Context(''));
135
136
        $this->assertInstanceOf(Array_::class, $resolvedType);
137
        $this->assertSame('array<string,object|array>', (string)$resolvedType);
138
139
        /** @var Array_ $valueType */
140
        $valueType = $resolvedType->getValueType();
141
142
        /** @var Compound $keyType */
143
        $keyType = $resolvedType->getKeyType();
144
145
        $this->assertInstanceOf(Types\String_::class, $keyType);
146
        $this->assertInstanceOf(Types\Compound::class, $valueType);
147
    }
148
149
    /**
150
     *
151
     * @covers ::__construct
152
     * @covers ::resolve
153
     *
154
     * @uses \phpDocumentor\Reflection\Types\Context
155
     * @uses \phpDocumentor\Reflection\Types\Compound
156
     * @uses \phpDocumentor\Reflection\Types\Collection
157
     * @uses \phpDocumentor\Reflection\Types\String_
158
     */
159
    public function testResolvingCollectionOfCollection() {
160
        $fixture = new TypeResolver();
161
162
        /** @var Collection $resolvedType */
163
        $resolvedType = $fixture->resolve('ArrayObject<string|integer|double,ArrayObject<DateTime>>', new Context(''));
164
165
        $this->assertInstanceOf(Collection::class, $resolvedType);
166
        $this->assertSame('\\ArrayObject<string|int|float,\\ArrayObject<\\DateTime>>', (string)$resolvedType);
167
168
        $this->assertEquals('\\ArrayObject', (string) $resolvedType->getFqsen());
169
170
        /** @var Collection $valueType */
171
        $valueType = $resolvedType->getValueType();
172
        $this->assertInstanceOf(Types\Collection::class, $valueType);
173
        $this->assertInstanceOf(Types\Object_::class, $valueType->getValueType());
174
        $this->assertEquals('\\ArrayObject', (string) $valueType->getFqsen());
175
        $this->assertEquals('\\DateTime', (string) $valueType->getValueType()->getFqsen());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface phpDocumentor\Reflection\Type as the method getFqsen() does only exist in the following implementations of said interface: phpDocumentor\Reflection\Types\Collection, phpDocumentor\Reflection\Types\Object_, phpDocumentor\Reflection\Types\Object_.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
176
177
        /** @var Compound $keyType */
178
        $keyType = $resolvedType->getKeyType();
179
        $this->assertInstanceOf(Types\Compound::class, $keyType);
180
        $this->assertInstanceOf(Types\String_::class, $keyType->get(0));
181
        $this->assertInstanceOf(Types\Integer::class, $keyType->get(1));
182
        $this->assertInstanceOf(Types\Float_::class, $keyType->get(2));
183
    }
184
185
    /**
186
     * @covers ::__construct
187
     * @covers ::resolve
188
     * @expectedException \RuntimeException
189
     * @expectedExceptionMessage An array can have only integers or strings as keys
190
     */
191
    public function testBadArrayCollectionKey()
192
    {
193
        $fixture = new TypeResolver();
194
        $fixture->resolve('array<object,string>', new Context(''));
195
    }
196
197
    /**
198
     * @covers ::__construct
199
     * @covers ::resolve
200
     * @expectedException \RuntimeException
201
     * @expectedExceptionMessage Unexpected collection operator "<", class name is missing
202
     */
203
    public function testMissingStartCollection()
204
    {
205
        $fixture = new TypeResolver();
206
        $fixture->resolve('<string>', new Context(''));
207
    }
208
209
    /**
210
     * @covers ::__construct
211
     * @covers ::resolve
212
     * @expectedException \RuntimeException
213
     * @expectedExceptionMessage Collection: ">" is missing
214
     */
215
    public function testMissingEndCollection()
216
    {
217
        $fixture = new TypeResolver();
218
        $fixture->resolve('ArrayObject<object|string', new Context(''));
219
    }
220
221
    /**
222
     * @covers ::__construct
223
     * @covers ::resolve
224
     * @expectedException \RuntimeException
225
     * @expectedExceptionMessage string is not a collection
226
     */
227
    public function testBadCollectionClass()
228
    {
229
        $fixture = new TypeResolver();
230
        $fixture->resolve('string<integer>', new Context(''));
231
    }
232
233
    /**
234
     *
235
     * @covers ::__construct
236
     * @covers ::resolve
237
     *
238
     * @uses \phpDocumentor\Reflection\Types\Context
239
     * @uses \phpDocumentor\Reflection\Types\Compound
240
     * @uses \phpDocumentor\Reflection\Types\Collection
241
     * @uses \phpDocumentor\Reflection\Types\String_
242
     */
243
    public function testResolvingCollectionAsArray() {
244
        $fixture = new TypeResolver();
245
246
        /** @var Collection $resolvedType */
247
        $resolvedType = $fixture->resolve('array<string,float>', new Context(''));
248
249
        $this->assertInstanceOf(Array_::class, $resolvedType);
250
        $this->assertSame('array<string,float>', (string)$resolvedType);
251
252
        /** @var Array_ $valueType */
253
        $valueType = $resolvedType->getValueType();
254
255
        /** @var Compound $keyType */
256
        $keyType = $resolvedType->getKeyType();
257
258
        $this->assertInstanceOf(Types\Float_::class, $valueType);
259
        $this->assertInstanceOf(Types\String_::class, $keyType);
260
    }
261
}
262