Completed
Push — master ( 9c9777...0675d2 )
by Jaap
02:23
created

testResolvingArrayExpressionOrCompoundTypes()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 35
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 35
rs 8.8571
c 1
b 0
f 0
cc 1
eloc 17
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 Mockery as m;
16
use phpDocumentor\Reflection\Types\Array_;
17
use phpDocumentor\Reflection\Types\Compound;
18
use phpDocumentor\Reflection\Types\Context;
19
use phpDocumentor\Reflection\Types\Iterable_;
20
use phpDocumentor\Reflection\Types\Nullable;
21
use phpDocumentor\Reflection\Types\Object_;
22
use phpDocumentor\Reflection\Types\Boolean;
23
use Mockery\MockInterface;
24
use phpDocumentor\Reflection\Types\String_;
25
26
/**
27
 * @coversDefaultClass phpDocumentor\Reflection\TypeResolver
28
 */
29
class TypeResolverTest extends \PHPUnit_Framework_TestCase
30
{
31
    /**
32
     * @param string $keyword
33
     * @param string $expectedClass
34
     *
35
     * @covers ::__construct
36
     * @covers ::resolve
37
     * @covers ::<private>
38
     *
39
     * @uses \phpDocumentor\Reflection\Types\Context
40
     * @uses \phpDocumentor\Reflection\Types\Array_
41
     * @uses \phpDocumentor\Reflection\Types\Object_
42
     *
43
     * @dataProvider provideKeywords
44
     */
45
    public function testResolvingKeywords($keyword, $expectedClass)
46
    {
47
        $fixture = new TypeResolver();
48
49
        $resolvedType = $fixture->resolve($keyword, new Context(''));
50
51
        $this->assertInstanceOf($expectedClass, $resolvedType);
52
    }
53
54
    /**
55
     * @param string $fqsen
56
     *
57
     * @covers ::__construct
58
     * @covers ::resolve
59
     * @covers ::<private>
60
     *
61
     * @uses \phpDocumentor\Reflection\Types\Context
62
     * @uses \phpDocumentor\Reflection\Types\Object_
63
     * @uses \phpDocumentor\Reflection\Fqsen
64
     * @uses \phpDocumentor\Reflection\FqsenResolver
65
     *
66
     * @dataProvider provideFqcn
67
     */
68
    public function testResolvingFQSENs($fqsen)
69
    {
70
        $fixture = new TypeResolver();
71
72
        /** @var Object_ $resolvedType */
73
        $resolvedType = $fixture->resolve($fqsen, new Context(''));
74
75
        $this->assertInstanceOf(Object_::class, $resolvedType);
76
        $this->assertInstanceOf(Fqsen::class, $resolvedType->getFqsen());
77
        $this->assertSame($fqsen, (string)$resolvedType);
78
    }
79
80
    /**
81
     * @covers ::__construct
82
     * @covers ::resolve
83
     * @covers ::<private>
84
     *
85
     * @uses \phpDocumentor\Reflection\Types\Context
86
     * @uses \phpDocumentor\Reflection\Types\Object_
87
     * @uses \phpDocumentor\Reflection\Fqsen
88
     * @uses \phpDocumentor\Reflection\FqsenResolver
89
     */
90
    public function testResolvingRelativeQSENsBasedOnNamespace()
91
    {
92
        $fixture = new TypeResolver();
93
94
        /** @var Object_ $resolvedType */
95
        $resolvedType = $fixture->resolve('DocBlock', new Context('phpDocumentor\Reflection'));
96
97
        $this->assertInstanceOf(Object_::class, $resolvedType);
98
        $this->assertInstanceOf(Fqsen::class, $resolvedType->getFqsen());
99
        $this->assertSame('\phpDocumentor\Reflection\DocBlock', (string)$resolvedType);
100
    }
101
102
    /**
103
     * @covers ::__construct
104
     * @covers ::resolve
105
     * @covers ::<private>
106
     *
107
     * @uses \phpDocumentor\Reflection\Types\Context
108
     * @uses \phpDocumentor\Reflection\Types\Object_
109
     * @uses \phpDocumentor\Reflection\Fqsen
110
     * @uses \phpDocumentor\Reflection\FqsenResolver
111
     */
112
    public function testResolvingRelativeQSENsBasedOnNamespaceAlias()
113
    {
114
        $fixture = new TypeResolver();
115
116
        /** @var Object_ $resolvedType */
117
        $resolvedType = $fixture->resolve(
118
            'm\MockInterface',
119
            new Context('phpDocumentor\Reflection', ['m' => m::class])
120
        );
121
122
        $this->assertInstanceOf(Object_::class, $resolvedType);
123
        $this->assertInstanceOf(Fqsen::class, $resolvedType->getFqsen());
124
        $this->assertSame('\Mockery\MockInterface', (string)$resolvedType);
125
    }
126
127
    /**
128
     * @covers ::__construct
129
     * @covers ::resolve
130
     * @covers ::<private>
131
     *
132
     * @uses \phpDocumentor\Reflection\Types\Context
133
     * @uses \phpDocumentor\Reflection\Types\Array_
134
     * @uses \phpDocumentor\Reflection\Types\String_
135
     */
136
    public function testResolvingTypedArrays()
137
    {
138
        $fixture = new TypeResolver();
139
140
        /** @var Array_ $resolvedType */
141
        $resolvedType = $fixture->resolve('string[]', new Context(''));
142
143
        $this->assertInstanceOf(Array_::class, $resolvedType);
144
        $this->assertSame('string[]', (string)$resolvedType);
145
        $this->assertInstanceOf(Compound::class, $resolvedType->getKeyType());
146
        $this->assertInstanceOf(Types\String_::class, $resolvedType->getValueType());
147
    }
148
149
    /**
150
     * @covers ::__construct
151
     * @covers ::resolve
152
     * @covers ::<private>
153
     *
154
     * @uses \phpDocumentor\Reflection\Types\Context
155
     * @uses \phpDocumentor\Reflection\Types\Nullable
156
     * @uses \phpDocumentor\Reflection\Types\String_
157
     */
158
    public function testResolvingNullableTypes()
159
    {
160
        $fixture = new TypeResolver();
161
162
        /** @var Nullable $resolvedType */
163
        $resolvedType = $fixture->resolve('?string', new Context(''));
164
165
        $this->assertInstanceOf(Nullable::class, $resolvedType);
166
        $this->assertInstanceOf(String_::class, $resolvedType->getActualType());
167
        $this->assertSame('?string', (string)$resolvedType);
168
    }
169
170
    /**
171
     * @covers ::__construct
172
     * @covers ::resolve
173
     * @covers ::<private>
174
     *
175
     * @uses phpDocumentor\Reflection\Types\Context
176
     * @uses phpDocumentor\Reflection\Types\Array_
177
     * @uses phpDocumentor\Reflection\Types\String_
178
     */
179
    public function testResolvingNestedTypedArrays()
180
    {
181
        $fixture = new TypeResolver();
182
183
        /** @var Array_ $resolvedType */
184
        $resolvedType = $fixture->resolve('string[][]', new Context(''));
185
186
        /** @var Array_ $childValueType */
187
        $childValueType = $resolvedType->getValueType();
188
189
        $this->assertInstanceOf(Array_::class, $resolvedType);
190
191
        $this->assertSame('string[][]', (string)$resolvedType);
192
        $this->assertInstanceOf(Compound::class, $resolvedType->getKeyType());
193
        $this->assertInstanceOf(Array_::class, $childValueType);
194
195
        $this->assertSame('string[]', (string)$childValueType);
196
        $this->assertInstanceOf(Compound::class, $childValueType->getKeyType());
197
        $this->assertInstanceOf(Types\String_::class, $childValueType->getValueType());
198
    }
199
200
    /**
201
     * @covers ::__construct
202
     * @covers ::resolve
203
     * @covers ::<private>
204
     *
205
     * @uses \phpDocumentor\Reflection\Types\Context
206
     * @uses \phpDocumentor\Reflection\Types\Compound
207
     * @uses \phpDocumentor\Reflection\Types\String_
208
     * @uses \phpDocumentor\Reflection\Types\Object_
209
     * @uses \phpDocumentor\Reflection\Fqsen
210
     * @uses \phpDocumentor\Reflection\FqsenResolver
211
     */
212
    public function testResolvingCompoundTypes()
213
    {
214
        $fixture = new TypeResolver();
215
216
        /** @var Compound $resolvedType */
217
        $resolvedType = $fixture->resolve('string|Reflection\DocBlock', new Context('phpDocumentor'));
218
219
        $this->assertInstanceOf(Compound::class, $resolvedType);
220
        $this->assertSame('string|\phpDocumentor\Reflection\DocBlock', (string)$resolvedType);
221
222
        /** @var String $secondType */
223
        $firstType = $resolvedType->get(0);
224
225
        /** @var Object_ $secondType */
226
        $secondType = $resolvedType->get(1);
227
228
        $this->assertInstanceOf(Types\String_::class, $firstType);
229
        $this->assertInstanceOf(Object_::class, $secondType);
230
        $this->assertInstanceOf(Fqsen::class, $secondType->getFqsen());
231
    }
232
233
    /**
234
     * @covers ::__construct
235
     * @covers ::resolve
236
     * @covers ::<private>
237
     *
238
     * @uses \phpDocumentor\Reflection\Types\Context
239
     * @uses \phpDocumentor\Reflection\Types\Compound
240
     * @uses \phpDocumentor\Reflection\Types\Array_
241
     * @uses \phpDocumentor\Reflection\Types\Object_
242
     * @uses \phpDocumentor\Reflection\Fqsen
243
     * @uses \phpDocumentor\Reflection\FqsenResolver
244
     */
245
    public function testResolvingCompoundTypedArrayTypes()
246
    {
247
        $fixture = new TypeResolver();
248
249
        /** @var Compound $resolvedType */
250
        $resolvedType = $fixture->resolve('\stdClass[]|Reflection\DocBlock[]', new Context('phpDocumentor'));
251
252
        $this->assertInstanceOf(Compound::class, $resolvedType);
253
        $this->assertSame('\stdClass[]|\phpDocumentor\Reflection\DocBlock[]', (string)$resolvedType);
254
255
        /** @var Array_ $firstType */
256
        $firstType = $resolvedType->get(0);
257
258
        /** @var Array_ $secondType */
259
        $secondType = $resolvedType->get(1);
260
261
        $this->assertInstanceOf(Array_::class, $firstType);
262
        $this->assertInstanceOf(Array_::class, $secondType);
263
        $this->assertInstanceOf(Object_::class, $firstType->getValueType());
264
        $this->assertInstanceOf(Object_::class, $secondType->getValueType());
265
    }
266
267
268
    /**
269
     * @covers ::__construct
270
     * @covers ::resolve
271
     * @covers ::<private>
272
     *
273
     * @uses \phpDocumentor\Reflection\Types\Context
274
     * @uses \phpDocumentor\Reflection\Types\Compound
275
     * @uses \phpDocumentor\Reflection\Types\Array_
276
     * @uses \phpDocumentor\Reflection\Types\Object_
277
     * @uses \phpDocumentor\Reflection\Fqsen
278
     * @uses \phpDocumentor\Reflection\FqsenResolver
279
     */
280
    public function testResolvingArrayExpressionObjectsTypes()
281
    {
282
        $fixture = new TypeResolver();
283
284
        /** @var Array_ $resolvedType */
285
        $resolvedType = $fixture->resolve('(\stdClass|Reflection\DocBlock)[]', new Context('phpDocumentor'));
286
287
        $this->assertInstanceOf(Array_::class, $resolvedType);
288
        $this->assertSame('(\stdClass|\phpDocumentor\Reflection\DocBlock)[]', (string)$resolvedType);
289
290
        /** @var Compound $valueType */
291
        $valueType = $resolvedType->getValueType();
292
293
        $this->assertInstanceOf(Compound::class, $valueType);
294
295
        /** @var Object_ $firstType */
296
        $firstType = $valueType->get(0);
297
298
        /** @var Object_ $secondType */
299
        $secondType = $valueType->get(1);
300
301
        $this->assertInstanceOf(Object_::class, $firstType);
302
        $this->assertInstanceOf(Object_::class, $secondType);
303
    }
304
305
    /**
306
     * @covers ::__construct
307
     * @covers ::resolve
308
     * @covers ::<private>
309
     *
310
     * @uses \phpDocumentor\Reflection\Types\Context
311
     * @uses \phpDocumentor\Reflection\Types\Compound
312
     * @uses \phpDocumentor\Reflection\Types\Array_
313
     * @uses \phpDocumentor\Reflection\Types\Object_
314
     * @uses \phpDocumentor\Reflection\Fqsen
315
     * @uses \phpDocumentor\Reflection\FqsenResolver
316
     */
317
    public function testResolvingArrayExpressionSimpleTypes()
318
    {
319
        $fixture = new TypeResolver();
320
321
        /** @var Array_ $resolvedType */
322
        $resolvedType = $fixture->resolve('(string|\stdClass|boolean)[]', new Context(''));
323
324
        $this->assertInstanceOf(Array_::class, $resolvedType);
325
        $this->assertSame('(string|\stdClass|bool)[]', (string)$resolvedType);
326
327
        /** @var Compound $valueType */
328
        $valueType = $resolvedType->getValueType();
329
330
        $this->assertInstanceOf(Compound::class, $valueType);
331
332
        /** @var String_ $firstType */
333
        $firstType = $valueType->get(0);
334
335
        /** @var Object_ $secondType */
336
        $secondType = $valueType->get(1);
337
338
        /** @var Boolean $thirdType */
339
        $thirdType = $valueType->get(2);
340
341
        $this->assertInstanceOf(String_::class, $firstType);
342
        $this->assertInstanceOf(Object_::class, $secondType);
343
        $this->assertInstanceOf(Boolean::class, $thirdType);
344
    }
345
346
    /**
347
     * @covers ::__construct
348
     * @covers ::resolve
349
     * @covers ::<private>
350
     *
351
     * @uses \phpDocumentor\Reflection\Types\Context
352
     * @uses \phpDocumentor\Reflection\Types\Compound
353
     * @uses \phpDocumentor\Reflection\Types\Array_
354
     * @uses \phpDocumentor\Reflection\Types\Object_
355
     * @uses \phpDocumentor\Reflection\Fqsen
356
     * @uses \phpDocumentor\Reflection\FqsenResolver
357
     */
358
    public function testResolvingArrayOfArrayExpressionTypes()
359
    {
360
        $fixture = new TypeResolver();
361
362
        /** @var Array_ $resolvedType */
363
        $resolvedType = $fixture->resolve('(string|\stdClass)[][]', new Context(''));
364
365
        $this->assertInstanceOf(Array_::class, $resolvedType);
366
        $this->assertSame('(string|\stdClass)[][]', (string)$resolvedType);
367
368
        /** @var Array_ $parentArrayType */
369
        $parentArrayType = $resolvedType->getValueType();
370
        $this->assertInstanceOf(Array_::class, $parentArrayType);
371
372
        /** @var Compound $valueType */
373
        $valueType = $parentArrayType->getValueType();
374
        $this->assertInstanceOf(Compound::class, $valueType);
375
376
        /** @var String_ $firstType */
377
        $firstType = $valueType->get(0);
378
379
        /** @var Object_ $secondType */
380
        $secondType = $valueType->get(1);
381
382
        $this->assertInstanceOf(String_::class, $firstType);
383
        $this->assertInstanceOf(Object_::class, $secondType);
384
    }
385
386
387
    /**
388
     * @covers ::__construct
389
     * @covers ::resolve
390
     * @covers ::<private>
391
     *
392
     * @uses \phpDocumentor\Reflection\Types\Context
393
     * @uses \phpDocumentor\Reflection\Types\Compound
394
     * @uses \phpDocumentor\Reflection\Types\Array_
395
     * @uses \phpDocumentor\Reflection\Types\Object_
396
     * @uses \phpDocumentor\Reflection\Fqsen
397
     * @uses \phpDocumentor\Reflection\FqsenResolver
398
     */
399
    public function testResolvingArrayExpressionOrCompoundTypes()
400
    {
401
        $fixture = new TypeResolver();
402
403
        /** @var Compound $resolvedType */
404
        $resolvedType = $fixture->resolve('\stdClass|(string|\stdClass)[]|bool', new Context(''));
405
406
        $this->assertInstanceOf(Compound::class, $resolvedType);
407
        $this->assertSame('\stdClass|(string|\stdClass)[]|bool', (string)$resolvedType);
408
409
        /** @var Object_ $firstType */
410
        $firstType = $resolvedType->get(0);
411
        $this->assertInstanceOf(Object_::class, $firstType);
412
413
        /** @var Array_ $secondType */
414
        $secondType = $resolvedType->get(1);
415
        $this->assertInstanceOf(Array_::class, $secondType);
416
417
        /** @var Array_ $thirdType */
418
        $thirdType = $resolvedType->get(2);
419
        $this->assertInstanceOf(Boolean::class, $thirdType);
420
421
        /** @var Compound $valueType */
422
        $valueType = $secondType->getValueType();
423
        $this->assertInstanceOf(Compound::class, $valueType);
424
425
        /** @var String_ $firstArrayType */
426
        $firstArrayType = $valueType->get(0);
427
428
        /** @var Object_ $secondArrayType */
429
        $secondArrayType = $valueType->get(1);
430
431
        $this->assertInstanceOf(String_::class, $firstArrayType);
432
        $this->assertInstanceOf(Object_::class, $secondArrayType);
433
    }
434
435
    /**
436
     * This test asserts that the parameter order is correct.
437
     *
438
     * When you pass two arrays separated by the compound operator (i.e. 'integer[]|string[]') then we always split the
439
     * expression in its compound parts and then we parse the types with the array operators. If we were to switch the
440
     * order around then 'integer[]|string[]' would read as an array of string or integer array; which is something
441
     * other than what we intend.
442
     *
443
     * @covers ::__construct
444
     * @covers ::resolve
445
     * @covers ::<private>
446
     *
447
     * @uses \phpDocumentor\Reflection\Types\Context
448
     * @uses \phpDocumentor\Reflection\Types\Compound
449
     * @uses \phpDocumentor\Reflection\Types\Array_
450
     * @uses \phpDocumentor\Reflection\Types\Integer
451
     * @uses \phpDocumentor\Reflection\Types\String_
452
     */
453
    public function testResolvingCompoundTypesWithTwoArrays()
454
    {
455
        $fixture = new TypeResolver();
456
457
        /** @var Compound $resolvedType */
458
        $resolvedType = $fixture->resolve('integer[]|string[]', new Context(''));
459
460
        $this->assertInstanceOf(Compound::class, $resolvedType);
461
        $this->assertSame('int[]|string[]', (string)$resolvedType);
462
463
        /** @var Array_ $firstType */
464
        $firstType = $resolvedType->get(0);
465
466
        /** @var Array_ $secondType */
467
        $secondType = $resolvedType->get(1);
468
469
        $this->assertInstanceOf(Array_::class, $firstType);
470
        $this->assertInstanceOf(Types\Integer::class, $firstType->getValueType());
471
        $this->assertInstanceOf(Array_::class, $secondType);
472
        $this->assertInstanceOf(Types\String_::class, $secondType->getValueType());
473
    }
474
475
    /**
476
     * @covers ::__construct
477
     * @covers ::addKeyword
478
     * @uses \phpDocumentor\Reflection\TypeResolver::resolve
479
     * @uses \phpDocumentor\Reflection\TypeResolver::<private>
480
     * @uses \phpDocumentor\Reflection\Types\Context
481
     */
482
    public function testAddingAKeyword()
483
    {
484
        // Assign
485
        $typeMock = m::mock(Type::class);
486
487
        // Act
488
        $fixture = new TypeResolver();
489
        $fixture->addKeyword('mock', get_class($typeMock));
490
491
        // Assert
492
        $result = $fixture->resolve('mock', new Context(''));
493
        $this->assertInstanceOf(get_class($typeMock), $result);
494
        $this->assertNotSame($typeMock, $result);
495
    }
496
497
    /**
498
     * @covers ::__construct
499
     * @covers ::addKeyword
500
     * @uses \phpDocumentor\Reflection\Types\Context
501
     * @expectedException \InvalidArgumentException
502
     */
503
    public function testAddingAKeywordFailsIfTypeClassDoesNotExist()
504
    {
505
        $fixture = new TypeResolver();
506
        $fixture->addKeyword('mock', 'IDoNotExist');
507
    }
508
509
    /**
510
     * @covers ::__construct
511
     * @covers ::addKeyword
512
     * @uses \phpDocumentor\Reflection\Types\Context
513
     * @expectedException \InvalidArgumentException
514
     */
515
    public function testAddingAKeywordFailsIfTypeClassDoesNotImplementTypeInterface()
516
    {
517
        $fixture = new TypeResolver();
518
        $fixture->addKeyword('mock', \stdClass::class);
519
    }
520
521
    /**
522
     * @covers ::__construct
523
     * @covers ::resolve
524
     * @uses \phpDocumentor\Reflection\Types\Context
525
     *
526
     * @expectedException \InvalidArgumentException
527
     */
528
    public function testExceptionIsThrownIfTypeIsEmpty()
529
    {
530
        $fixture = new TypeResolver();
531
        $fixture->resolve(' ', new Context(''));
532
    }
533
534
    /**
535
     * @covers ::__construct
536
     * @covers ::resolve
537
     * @uses \phpDocumentor\Reflection\Types\Context
538
     *
539
     * @expectedException \InvalidArgumentException
540
     */
541
    public function testExceptionIsThrownIfTypeIsNotAString()
542
    {
543
        $fixture = new TypeResolver();
544
        $fixture->resolve(['a'], new Context(''));
545
    }
546
547
    /**
548
     * Returns a list of keywords and expected classes that are created from them.
549
     *
550
     * @return string[][]
551
     */
552
    public function provideKeywords()
553
    {
554
        return [
555
            ['string', Types\String_::class],
556
            ['int', Types\Integer::class],
557
            ['integer', Types\Integer::class],
558
            ['float', Types\Float_::class],
559
            ['double', Types\Float_::class],
560
            ['bool', Types\Boolean::class],
561
            ['boolean', Types\Boolean::class],
562
            ['resource', Types\Resource_::class],
563
            ['null', Types\Null_::class],
564
            ['callable', Types\Callable_::class],
565
            ['callback', Types\Callable_::class],
566
            ['array', Array_::class],
567
            ['scalar', Types\Scalar::class],
568
            ['object', Object_::class],
569
            ['mixed', Types\Mixed_::class],
570
            ['void', Types\Void_::class],
571
            ['$this', Types\This::class],
572
            ['static', Types\Static_::class],
573
            ['self', Types\Self_::class],
574
            ['parent', Types\Parent_::class],
575
            ['iterable', Iterable_::class],
576
        ];
577
    }
578
579
    /**
580
     * Provides a list of FQSENs to test the resolution patterns with.
581
     *
582
     * @return string[][]
583
     */
584
    public function provideFqcn()
585
    {
586
        return [
587
            'namespace' => ['\phpDocumentor\Reflection'],
588
            'class'     => ['\phpDocumentor\Reflection\DocBlock'],
589
        ];
590
    }
591
}
592