Completed
Push — master ( e555ac...eb34a4 )
by Jaap
01:16
created

TypeResolverTest::tearDown()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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