10
10
use PhpParser \Node ;
11
11
use PhpParser \Node \Arg ;
12
12
use PhpParser \Node \AttributeGroup ;
13
+ use PhpParser \Node \ComplexType ;
13
14
use PhpParser \Node \Expr ;
14
15
use PhpParser \Node \Expr \Array_ ;
15
16
use PhpParser \Node \Expr \ArrayDimFetch ;
35
36
use PhpParser \Node \Expr \StaticPropertyFetch ;
36
37
use PhpParser \Node \Expr \Ternary ;
37
38
use PhpParser \Node \Expr \Variable ;
39
+ use PhpParser \Node \Identifier ;
38
40
use PhpParser \Node \Name ;
39
41
use PhpParser \Node \Stmt \Break_ ;
40
42
use PhpParser \Node \Stmt \Class_ ;
98
100
use PHPStan \Node \InClosureNode ;
99
101
use PHPStan \Node \InForeachNode ;
100
102
use PHPStan \Node \InFunctionNode ;
103
+ use PHPStan \Node \InPropertyHookNode ;
101
104
use PHPStan \Node \InstantiationCallableNode ;
102
105
use PHPStan \Node \InTraitNode ;
103
106
use PHPStan \Node \InvalidateExprNode ;
@@ -642,6 +645,8 @@ private function processStmtNode(
642
645
throw new ShouldNotHappenException ();
643
646
}
644
647
648
+ $ classReflection = $ scope ->getClassReflection ();
649
+
645
650
$ isFromTrait = $ stmt ->getAttribute ('originalTraitMethodName ' ) === '__construct ' ;
646
651
if ($ isFromTrait || $ stmt ->name ->toLowerString () === '__construct ' ) {
647
652
foreach ($ stmt ->params as $ param ) {
@@ -659,7 +664,7 @@ private function processStmtNode(
659
664
$ nodeCallback (new ClassPropertyNode (
660
665
$ param ->var ->name ,
661
666
$ param ->flags ,
662
- $ param ->type !== null ? ParserNodeTypeToPHPStanType::resolve ($ param ->type , $ scope -> getClassReflection () ) : null ,
667
+ $ param ->type !== null ? ParserNodeTypeToPHPStanType::resolve ($ param ->type , $ classReflection ) : null ,
663
668
null ,
664
669
$ phpDoc ,
665
670
$ phpDocParameterTypes [$ param ->var ->name ] ?? null ,
@@ -668,10 +673,19 @@ private function processStmtNode(
668
673
$ param ,
669
674
false ,
670
675
$ scope ->isInTrait (),
671
- $ scope -> getClassReflection () ->isReadOnly (),
676
+ $ classReflection ->isReadOnly (),
672
677
false ,
673
- $ scope -> getClassReflection () ,
678
+ $ classReflection ,
674
679
), $ methodScope );
680
+ $ this ->processPropertyHooks (
681
+ $ stmt ,
682
+ $ param ->type ,
683
+ $ phpDocParameterTypes [$ param ->var ->name ] ?? null ,
684
+ $ param ->var ->name ,
685
+ $ param ->hooks ,
686
+ $ scope ,
687
+ $ nodeCallback ,
688
+ );
675
689
$ methodScope = $ methodScope ->assignExpression (new PropertyInitializationExpr ($ param ->var ->name ), new MixedType (), new MixedType ());
676
690
}
677
691
}
@@ -681,7 +695,7 @@ private function processStmtNode(
681
695
if (!$ methodReflection instanceof PhpMethodFromParserNodeReflection) {
682
696
throw new ShouldNotHappenException ();
683
697
}
684
- $ nodeCallback (new InClassMethodNode ($ scope -> getClassReflection () , $ methodReflection , $ stmt ), $ methodScope );
698
+ $ nodeCallback (new InClassMethodNode ($ classReflection , $ methodReflection , $ stmt ), $ methodScope );
685
699
}
686
700
687
701
if ($ stmt ->stmts !== null ) {
@@ -730,8 +744,6 @@ private function processStmtNode(
730
744
$ gatheredReturnStatements [] = new ReturnStatement ($ scope , $ node );
731
745
}, StatementContext::createTopLevel ());
732
746
733
- $ classReflection = $ scope ->getClassReflection ();
734
-
735
747
$ methodReflection = $ methodScope ->getFunction ();
736
748
if (!$ methodReflection instanceof PhpMethodFromParserNodeReflection) {
737
749
throw new ShouldNotHappenException ();
@@ -893,29 +905,38 @@ private function processStmtNode(
893
905
$ impurePoints = [];
894
906
$ this ->processAttributeGroups ($ stmt , $ stmt ->attrGroups , $ scope , $ nodeCallback );
895
907
908
+ $ nativePropertyType = $ stmt ->type !== null ? ParserNodeTypeToPHPStanType::resolve ($ stmt ->type , $ scope ->getClassReflection ()) : null ;
909
+
910
+ [,,,,,,,,,,,,$ isReadOnly , $ docComment , ,,,$ varTags , $ isAllowedPrivateMutation ] = $ this ->getPhpDocs ($ scope , $ stmt );
911
+ $ phpDocType = null ;
912
+ if (isset ($ varTags [0 ]) && count ($ varTags ) === 1 ) {
913
+ $ phpDocType = $ varTags [0 ]->getType ();
914
+ }
915
+
896
916
foreach ($ stmt ->props as $ prop ) {
897
917
$ nodeCallback ($ prop , $ scope );
898
918
if ($ prop ->default !== null ) {
899
919
$ this ->processExprNode ($ stmt , $ prop ->default , $ scope , $ nodeCallback , ExpressionContext::createDeep ());
900
920
}
901
- [,,,,,,,,,,,, $ isReadOnly , $ docComment , ,,, $ varTags , $ isAllowedPrivateMutation ] = $ this -> getPhpDocs ( $ scope , $ stmt );
921
+
902
922
if (!$ scope ->isInClass ()) {
903
923
throw new ShouldNotHappenException ();
904
924
}
905
925
$ propertyName = $ prop ->name ->toString ();
906
- $ phpDocType = null ;
907
- if (isset ( $ varTags [ 0 ]) && count ( $ varTags ) === 1 ) {
908
- $ phpDocType = $ varTags [0 ]-> getType ();
909
- } elseif ( isset ( $ varTags [$ propertyName ])) {
910
- $ phpDocType = $ varTags [ $ propertyName ]-> getType ();
926
+
927
+ if ($ phpDocType === null ) {
928
+ if ( isset ( $ varTags [$ propertyName ])) {
929
+ $ phpDocType = $ varTags [$ propertyName ]-> getType ();
930
+ }
911
931
}
932
+
912
933
$ propStmt = clone $ stmt ;
913
934
$ propStmt ->setAttributes ($ prop ->getAttributes ());
914
935
$ nodeCallback (
915
936
new ClassPropertyNode (
916
937
$ propertyName ,
917
938
$ stmt ->flags ,
918
- $ stmt -> type !== null ? ParserNodeTypeToPHPStanType:: resolve ( $ stmt -> type , $ scope -> getClassReflection ()) : null ,
939
+ $ nativePropertyType ,
919
940
$ prop ->default ,
920
941
$ docComment ,
921
942
$ phpDocType ,
@@ -932,6 +953,21 @@ private function processStmtNode(
932
953
);
933
954
}
934
955
956
+ if (count ($ stmt ->hooks ) > 0 ) {
957
+ if (!isset ($ propertyName )) {
958
+ throw new ShouldNotHappenException ('Property name should be known when analysing hooks. ' );
959
+ }
960
+ $ this ->processPropertyHooks (
961
+ $ stmt ,
962
+ $ stmt ->type ,
963
+ $ phpDocType ,
964
+ $ propertyName ,
965
+ $ stmt ->hooks ,
966
+ $ scope ,
967
+ $ nodeCallback ,
968
+ );
969
+ }
970
+
935
971
if ($ stmt ->type !== null ) {
936
972
$ nodeCallback ($ stmt ->type , $ scope );
937
973
}
@@ -4614,6 +4650,60 @@ private function processAttributeGroups(
4614
4650
}
4615
4651
}
4616
4652
4653
+ /**
4654
+ * @param Node\PropertyHook[] $hooks
4655
+ * @param callable(Node $node, Scope $scope): void $nodeCallback
4656
+ */
4657
+ private function processPropertyHooks (
4658
+ Node \Stmt $ stmt ,
4659
+ Identifier |Name |ComplexType |null $ nativeTypeNode ,
4660
+ ?Type $ phpDocType ,
4661
+ string $ propertyName ,
4662
+ array $ hooks ,
4663
+ MutatingScope $ scope ,
4664
+ callable $ nodeCallback ,
4665
+ ): void
4666
+ {
4667
+ if (!$ scope ->isInClass ()) {
4668
+ throw new ShouldNotHappenException ();
4669
+ }
4670
+
4671
+ $ classReflection = $ scope ->getClassReflection ();
4672
+
4673
+ foreach ($ hooks as $ hook ) {
4674
+ $ nodeCallback ($ hook , $ scope );
4675
+ $ this ->processAttributeGroups ($ stmt , $ hook ->attrGroups , $ scope , $ nodeCallback );
4676
+
4677
+ [, $ phpDocParameterTypes ,,,, $ phpDocThrowType ,,,,,,,, $ phpDocComment ] = $ this ->getPhpDocs ($ scope , $ hook );
4678
+
4679
+ foreach ($ hook ->params as $ param ) {
4680
+ $ this ->processParamNode ($ stmt , $ param , $ scope , $ nodeCallback );
4681
+ }
4682
+
4683
+ $ hookScope = $ scope ->enterPropertyHook (
4684
+ $ hook ,
4685
+ $ propertyName ,
4686
+ $ nativeTypeNode ,
4687
+ $ phpDocType ,
4688
+ $ phpDocParameterTypes ,
4689
+ $ phpDocThrowType ,
4690
+ $ phpDocComment ,
4691
+ );
4692
+ $ hookReflection = $ hookScope ->getFunction ();
4693
+ if (!$ hookReflection instanceof PhpMethodFromParserNodeReflection) {
4694
+ throw new ShouldNotHappenException ();
4695
+ }
4696
+ $ nodeCallback (new InPropertyHookNode ($ classReflection , $ hookReflection , $ hook ), $ hookScope );
4697
+
4698
+ if ($ hook ->body instanceof Expr) {
4699
+ $ this ->processExprNode ($ stmt , $ hook ->body , $ hookScope , $ nodeCallback , ExpressionContext::createTopLevel ());
4700
+ } elseif (is_array ($ hook ->body )) {
4701
+ $ this ->processStmtNodes ($ stmt , $ hook ->body , $ hookScope , $ nodeCallback , StatementContext::createTopLevel ());
4702
+ }
4703
+
4704
+ }
4705
+ }
4706
+
4617
4707
/**
4618
4708
* @param MethodReflection|FunctionReflection|null $calleeReflection
4619
4709
* @param callable(Node $node, Scope $scope): void $nodeCallback
0 commit comments