27
27
*/
28
28
class DebugClassLoader
29
29
{
30
+ private const COMMON_NON_OBJECT_RETURNED_TYPES = [
31
+ 'false ' ,
32
+ 'mixed ' ,
33
+ 'null ' ,
34
+ 'resource ' ,
35
+ 'static ' ,
36
+ 'true ' ,
37
+ '$this '
38
+ ];
39
+
40
+ private const NON_NULLABLE_RETURNABLE_TYPES = [
41
+ 'void ' => '"void" ' ,
42
+ ];
43
+
44
+ private const NULLABLE_RETURNABLE_TYPES = [
45
+ 'array ' => 'an array ' ,
46
+ 'bool ' => 'a boolean ' ,
47
+ 'callable ' => 'a callable ' ,
48
+ 'float ' => 'a float ' ,
49
+ 'int ' => 'an integer ' ,
50
+ 'iterable ' => 'an iterable ' ,
51
+ 'object ' => 'an object ' ,
52
+ 'string ' => 'a string ' ,
53
+ 'self ' => 'an instance of itself ' ,
54
+ ];
55
+
30
56
private $ classLoader ;
31
57
private $ isFinder ;
32
58
private $ loaded = [];
@@ -40,6 +66,7 @@ class DebugClassLoader
40
66
private static $ annotatedParameters = [];
41
67
private static $ darwinCache = ['/ ' => ['/ ' , []]];
42
68
private static $ method = [];
69
+ private static $ returnTypes = [];
43
70
44
71
public function __construct (callable $ classLoader )
45
72
{
@@ -309,6 +336,8 @@ public function checkAnnotations(\ReflectionClass $refl, $class)
309
336
}
310
337
}
311
338
339
+ $ this ->checkReturnTypes ($ class , $ parent , $ refl , $ ns , $ len , $ deprecations );
340
+
312
341
if (\trait_exists ($ class )) {
313
342
return $ deprecations ;
314
343
}
@@ -372,6 +401,8 @@ public function checkAnnotations(\ReflectionClass $refl, $class)
372
401
}
373
402
}
374
403
404
+ $ this ->saveReturnType ($ class , $ method , $ doc );
405
+
375
406
if ($ finalOrInternal || $ method ->isConstructor () || false === \strpos ($ doc , '@param ' ) || StatelessInvocation::class === $ class ) {
376
407
continue ;
377
408
}
@@ -522,4 +553,152 @@ private function getOwnInterfaces($class, $parent)
522
553
523
554
return $ ownInterfaces ;
524
555
}
556
+
557
+ private function saveReturnType (string $ class , \ReflectionMethod $ method , string $ doc ): void
558
+ {
559
+ if ($ method ->getReturnType () instanceof \ReflectionType) {
560
+ return ;
561
+ }
562
+
563
+ if (false === \strpos ($ doc , 'return ' )) {
564
+ return ;
565
+ }
566
+
567
+ if (!preg_match_all ('/\n\s+\* @return +(\S+)/ ' , $ doc ,$ matches , PREG_SET_ORDER )) {
568
+ return ;
569
+ }
570
+
571
+ $ types = explode ('| ' , end ($ matches )[1 ], 3 );
572
+
573
+ $ nullable = false ;
574
+
575
+ switch (count ($ types )) {
576
+ case 1 :
577
+ $ type = $ types [0 ];
578
+
579
+ break ;
580
+ case 2 :
581
+ // fastest way to handle the null|null edge case
582
+ $ type = 'null ' ;
583
+
584
+ foreach (array_unique (array_map ([$ this , 'normalizeType ' ], $ types )) as $ i => $ normalizedType ) {
585
+ if ('null ' === $ normalizedType ) {
586
+ $ nullable = true ;
587
+ } else {
588
+ $ type = $ types [$ i ];
589
+ }
590
+ }
591
+
592
+ if (!$ nullable ) {
593
+ return ;
594
+ }
595
+
596
+ break ;
597
+ default :
598
+ return ;
599
+ }
600
+
601
+ $ normalizedType = $ this ->normalizeType ($ type );
602
+ if (in_array ($ normalizedType , self ::COMMON_NON_OBJECT_RETURNED_TYPES )) {
603
+ return ;
604
+ }
605
+
606
+ if (isset (self ::NULLABLE_RETURNABLE_TYPES [$ normalizedType ])) {
607
+ $ normalizedDocReturnType = self ::NULLABLE_RETURNABLE_TYPES [$ normalizedType ];
608
+ $ hintedReturnType = $ normalizedType ;
609
+ } elseif (isset (self ::NON_NULLABLE_RETURNABLE_TYPES [$ normalizedType ])) {
610
+ if ($ nullable ) {
611
+ return ;
612
+ }
613
+
614
+ $ normalizedDocReturnType = self ::NON_NULLABLE_RETURNABLE_TYPES [$ normalizedType ];
615
+ $ hintedReturnType = $ normalizedType ;
616
+ } else {
617
+ $ normalizedDocReturnType = sprintf ('an instance of "%s" ' , $ type );
618
+ $ hintedReturnType = $ type ;
619
+ }
620
+
621
+ if ($ nullable ) {
622
+ $ normalizedDocReturnType .= ' or null ' ;
623
+ $ hintedReturnType = '? ' .$ hintedReturnType ;
624
+ }
625
+
626
+ self ::$ returnTypes [$ class ][$ method ->name ] = [$ normalizedDocReturnType , $ hintedReturnType ];
627
+ }
628
+
629
+ /**
630
+ * @param string $class
631
+ * @param string|false $parent
632
+ * @param \ReflectionClass $refl
633
+ * @param string $ns
634
+ * @param int $len
635
+ * @param array $deprecations
636
+ */
637
+ private function checkReturnTypes (string $ class , $ parent , \ReflectionClass $ refl , string $ ns , int $ len , &$ deprecations ): void
638
+ {
639
+ $ parentsAndInterfacesThatHaveMethodsThatReturnsADocType = [];
640
+ foreach (array_merge ($ parent ? array_merge ([$ parent ], class_parents ($ parent , false )) : [], class_implements ($ class , false )) as $ use ) {
641
+ if (isset (self ::$ returnTypes [$ use ]) && 0 !== \strncmp ($ ns , \str_replace ('_ ' , '\\' , $ use ), $ len )) {
642
+ $ parentsAndInterfacesThatHaveMethodsThatReturnsADocType [] = $ use ;
643
+ }
644
+ }
645
+
646
+ if (empty ($ parentsAndInterfacesThatHaveMethodsThatReturnsADocType )) {
647
+ return ;
648
+ }
649
+
650
+ foreach ($ refl ->getMethods (\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED ) as $ method ) {
651
+ if ($ method ->class !== $ class || $ method ->getReturnType () instanceof \ReflectionType) {
652
+ continue ;
653
+ }
654
+
655
+ foreach ($ parentsAndInterfacesThatHaveMethodsThatReturnsADocType as $ name ) {
656
+ if (!isset (self ::$ returnTypes [$ name ][$ method ->name ])) {
657
+ continue ;
658
+ }
659
+
660
+ list ($ normalizedDocReturnType , $ hintedReturnType ) = self ::$ returnTypes [$ name ][$ method ->name ];
661
+ $ deprecations [] = sprintf (
662
+ 'The "%s::%s()" parent method returns %s. You could safely add the ": %s" return type to your method to be compatible with the next major version of its parent. ' ,
663
+ $ class ,
664
+ $ method ->name ,
665
+ $ normalizedDocReturnType ,
666
+ $ hintedReturnType
667
+ );
668
+
669
+ break ;
670
+ }
671
+ }
672
+ }
673
+
674
+ private function normalizeType (string $ type ): string
675
+ {
676
+ $ lcType = mb_strtolower ($ type );
677
+
678
+ if (
679
+ isset (self ::NULLABLE_RETURNABLE_TYPES [$ lcType ]) ||
680
+ isset (self ::NON_NULLABLE_RETURNABLE_TYPES [$ lcType ]) ||
681
+ isset (self ::COMMON_NON_OBJECT_RETURNED_TYPES [$ lcType ]))
682
+ {
683
+ return $ lcType ;
684
+ }
685
+
686
+ if ('[] ' === substr ($ lcType , -2 )) {
687
+ return 'array ' ;
688
+ }
689
+
690
+ if ('integer ' === $ lcType ) {
691
+ return 'int ' ;
692
+ }
693
+
694
+ if ('boolean ' === $ lcType ) {
695
+ return 'bool ' ;
696
+ }
697
+
698
+ if (1 === preg_match ('/^callable *\(/ ' , $ lcType )) {
699
+ return 'callable ' ;
700
+ }
701
+
702
+ return $ lcType ;
703
+ }
525
704
}
0 commit comments