[] */ private const ALLOWED_NOT_READONLY_ANNOTATION_CLASS_OR_ATTRIBUTES = ['Doctrine\\ORM\\Mapping\\Entity', 'Doctrine\\ORM\\Mapping\\Table']; /** * @var string[]|class-string[] */ private const ALLOWED_READONLY_ANNOTATION_CLASS_OR_ATTRIBUTES = ['Doctrine\\ORM\\Mapping\\Id', 'Doctrine\\ORM\\Mapping\\Column', 'Doctrine\\ORM\\Mapping\\OneToMany', 'Doctrine\\ORM\\Mapping\\ManyToMany', 'Doctrine\\ORM\\Mapping\\ManyToOne', 'Doctrine\\ORM\\Mapping\\OneToOne', 'JMS\\Serializer\\Annotation\\Type']; /** * @readonly * @var \Rector\Core\NodeManipulator\AssignManipulator */ private $assignManipulator; /** * @readonly * @var \Rector\Core\PhpParser\Node\BetterNodeFinder */ private $betterNodeFinder; /** * @readonly * @var \Rector\ReadWrite\Guard\VariableToConstantGuard */ private $variableToConstantGuard; /** * @readonly * @var \Rector\ReadWrite\NodeAnalyzer\ReadWritePropertyAnalyzer */ private $readWritePropertyAnalyzer; /** * @readonly * @var \Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory */ private $phpDocInfoFactory; /** * @readonly * @var \Symplify\PackageBuilder\Php\TypeChecker */ private $typeChecker; /** * @readonly * @var \Rector\Core\PhpParser\NodeFinder\PropertyFetchFinder */ private $propertyFetchFinder; /** * @readonly * @var \Rector\Core\Reflection\ReflectionResolver */ private $reflectionResolver; /** * @readonly * @var \Rector\NodeNameResolver\NodeNameResolver */ private $nodeNameResolver; /** * @readonly * @var \Rector\Php80\NodeAnalyzer\PhpAttributeAnalyzer */ private $phpAttributeAnalyzer; /** * @readonly * @var \Rector\NodeTypeResolver\NodeTypeResolver */ private $nodeTypeResolver; /** * @readonly * @var \Rector\Php80\NodeAnalyzer\PromotedPropertyResolver */ private $promotedPropertyResolver; /** * @readonly * @var \Rector\TypeDeclaration\AlreadyAssignDetector\ConstructorAssignDetector */ private $constructorAssignDetector; /** * @readonly * @var \Rector\Core\PhpParser\AstResolver */ private $astResolver; /** * @readonly * @var \Rector\Core\NodeAnalyzer\PropertyFetchAnalyzer */ private $propertyFetchAnalyzer; public function __construct(\Rector\Core\NodeManipulator\AssignManipulator $assignManipulator, \Rector\Core\PhpParser\Node\BetterNodeFinder $betterNodeFinder, \Rector\ReadWrite\Guard\VariableToConstantGuard $variableToConstantGuard, \Rector\ReadWrite\NodeAnalyzer\ReadWritePropertyAnalyzer $readWritePropertyAnalyzer, \Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory $phpDocInfoFactory, \RectorPrefix20220606\Symplify\PackageBuilder\Php\TypeChecker $typeChecker, \Rector\Core\PhpParser\NodeFinder\PropertyFetchFinder $propertyFetchFinder, \Rector\Core\Reflection\ReflectionResolver $reflectionResolver, \Rector\NodeNameResolver\NodeNameResolver $nodeNameResolver, \Rector\Php80\NodeAnalyzer\PhpAttributeAnalyzer $phpAttributeAnalyzer, \Rector\NodeTypeResolver\NodeTypeResolver $nodeTypeResolver, \Rector\Php80\NodeAnalyzer\PromotedPropertyResolver $promotedPropertyResolver, \Rector\TypeDeclaration\AlreadyAssignDetector\ConstructorAssignDetector $constructorAssignDetector, \Rector\Core\PhpParser\AstResolver $astResolver, \Rector\Core\NodeAnalyzer\PropertyFetchAnalyzer $propertyFetchAnalyzer) { $this->assignManipulator = $assignManipulator; $this->betterNodeFinder = $betterNodeFinder; $this->variableToConstantGuard = $variableToConstantGuard; $this->readWritePropertyAnalyzer = $readWritePropertyAnalyzer; $this->phpDocInfoFactory = $phpDocInfoFactory; $this->typeChecker = $typeChecker; $this->propertyFetchFinder = $propertyFetchFinder; $this->reflectionResolver = $reflectionResolver; $this->nodeNameResolver = $nodeNameResolver; $this->phpAttributeAnalyzer = $phpAttributeAnalyzer; $this->nodeTypeResolver = $nodeTypeResolver; $this->promotedPropertyResolver = $promotedPropertyResolver; $this->constructorAssignDetector = $constructorAssignDetector; $this->astResolver = $astResolver; $this->propertyFetchAnalyzer = $propertyFetchAnalyzer; } /** * @param \PhpParser\Node\Stmt\Property|\PhpParser\Node\Param $propertyOrPromotedParam */ public function isAllowedReadOnly($propertyOrPromotedParam, \Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo $phpDocInfo) : bool { if ($phpDocInfo->hasByAnnotationClasses(self::ALLOWED_READONLY_ANNOTATION_CLASS_OR_ATTRIBUTES)) { return \true; } return $this->phpAttributeAnalyzer->hasPhpAttributes($propertyOrPromotedParam, self::ALLOWED_READONLY_ANNOTATION_CLASS_OR_ATTRIBUTES); } /** * @param \PhpParser\Node\Stmt\Property|\PhpParser\Node\Param $propertyOrPromotedParam */ public function isPropertyUsedInReadContext(\PhpParser\Node\Stmt\Class_ $class, $propertyOrPromotedParam) : bool { $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($propertyOrPromotedParam); if ($this->isAllowedReadOnly($propertyOrPromotedParam, $phpDocInfo)) { return \true; } $privatePropertyFetches = $this->propertyFetchFinder->findPrivatePropertyFetches($class, $propertyOrPromotedParam); foreach ($privatePropertyFetches as $privatePropertyFetch) { if ($this->readWritePropertyAnalyzer->isRead($privatePropertyFetch)) { return \true; } } // has classLike $this->$variable call? $classLike = $this->betterNodeFinder->findParentType($propertyOrPromotedParam, \PhpParser\Node\Stmt\ClassLike::class); if (!$classLike instanceof \PhpParser\Node\Stmt\ClassLike) { return \false; } return (bool) $this->betterNodeFinder->findFirst($classLike->stmts, function (\PhpParser\Node $node) : bool { if (!$node instanceof \PhpParser\Node\Expr\PropertyFetch) { return \false; } if (!$this->readWritePropertyAnalyzer->isRead($node)) { return \false; } return $node->name instanceof \PhpParser\Node\Expr; }); } /** * @param \PhpParser\Node\Stmt\Property|\PhpParser\Node\Param $propertyOrParam */ public function isPropertyChangeableExceptConstructor($propertyOrParam) : bool { $class = $this->betterNodeFinder->findParentType($propertyOrParam, \PhpParser\Node\Stmt\Class_::class); // does not has parent type ClassLike? Possibly parent is changed by other rule if (!$class instanceof \PhpParser\Node\Stmt\Class_) { return \true; } $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($class); if ($phpDocInfo->hasByAnnotationClasses(self::ALLOWED_NOT_READONLY_ANNOTATION_CLASS_OR_ATTRIBUTES)) { return \true; } if ($this->phpAttributeAnalyzer->hasPhpAttributes($class, self::ALLOWED_NOT_READONLY_ANNOTATION_CLASS_OR_ATTRIBUTES)) { return \true; } $propertyFetches = $this->propertyFetchFinder->findPrivatePropertyFetches($class, $propertyOrParam); foreach ($propertyFetches as $propertyFetch) { if ($this->isChangeableContext($propertyFetch)) { return \true; } // skip for constructor? it is allowed to set value in constructor method $propertyName = (string) $this->nodeNameResolver->getName($propertyFetch); $classMethod = $this->betterNodeFinder->findParentType($propertyFetch, \PhpParser\Node\Stmt\ClassMethod::class); if ($this->isPropertyAssignedOnlyInConstructor($class, $propertyName, $classMethod)) { continue; } if ($this->assignManipulator->isLeftPartOfAssign($propertyFetch)) { return \true; } $isInUnset = (bool) $this->betterNodeFinder->findParentType($propertyFetch, \PhpParser\Node\Stmt\Unset_::class); if ($isInUnset) { return \true; } } return \false; } public function isPropertyChangeable(\PhpParser\Node\Stmt\Class_ $class, \PhpParser\Node\Stmt\Property $property) : bool { $propertyFetches = $this->propertyFetchFinder->findPrivatePropertyFetches($class, $property); foreach ($propertyFetches as $propertyFetch) { if ($this->isChangeableContext($propertyFetch)) { return \true; } if ($this->assignManipulator->isLeftPartOfAssign($propertyFetch)) { return \true; } } return \false; } public function resolveExistingClassPropertyNameByType(\PhpParser\Node\Stmt\Class_ $class, \PHPStan\Type\Type $type) : ?string { foreach ($class->getProperties() as $property) { $propertyType = $this->nodeTypeResolver->getType($property); if (!$propertyType->equals($type)) { continue; } return $this->nodeNameResolver->getName($property); } $promotedPropertyParams = $this->promotedPropertyResolver->resolveFromClass($class); foreach ($promotedPropertyParams as $promotedPropertyParam) { $paramType = $this->nodeTypeResolver->getType($promotedPropertyParam); if (!$paramType->equals($type)) { continue; } return $this->nodeNameResolver->getName($promotedPropertyParam); } return null; } public function isUsedByTrait(\PHPStan\Reflection\ClassReflection $classReflection, string $propertyName) : bool { foreach ($classReflection->getTraits() as $traitUse) { $trait = $this->astResolver->resolveClassFromName($traitUse->getName()); if (!$trait instanceof \PhpParser\Node\Stmt\Trait_) { continue; } if ($this->propertyFetchAnalyzer->containsLocalPropertyFetchName($trait, $propertyName)) { return \true; } } return \false; } private function isPropertyAssignedOnlyInConstructor(\PhpParser\Node\Stmt\Class_ $class, string $propertyName, ?\PhpParser\Node\Stmt\ClassMethod $classMethod) : bool { if (!$classMethod instanceof \PhpParser\Node\Stmt\ClassMethod) { return \false; } // there is property unset in Test class, so only check on __construct if (!$this->nodeNameResolver->isName($classMethod->name, \Rector\Core\ValueObject\MethodName::CONSTRUCT)) { return \false; } return $this->constructorAssignDetector->isPropertyAssigned($class, $propertyName); } /** * @param \PhpParser\Node\Expr\PropertyFetch|\PhpParser\Node\Expr\StaticPropertyFetch $propertyFetch */ private function isChangeableContext($propertyFetch) : bool { $parent = $propertyFetch->getAttribute(\Rector\NodeTypeResolver\Node\AttributeKey::PARENT_NODE); if (!$parent instanceof \PhpParser\Node) { return \false; } if ($this->typeChecker->isInstanceOf($parent, [\PhpParser\Node\Expr\PreInc::class, \PhpParser\Node\Expr\PreDec::class, \PhpParser\Node\Expr\PostInc::class, \PhpParser\Node\Expr\PostDec::class])) { $parent = $parent->getAttribute(\Rector\NodeTypeResolver\Node\AttributeKey::PARENT_NODE); } if (!$parent instanceof \PhpParser\Node) { return \false; } if ($parent instanceof \PhpParser\Node\Arg) { $readArg = $this->variableToConstantGuard->isReadArg($parent); if (!$readArg) { return \true; } $caller = $parent->getAttribute(\Rector\NodeTypeResolver\Node\AttributeKey::PARENT_NODE); if ($caller instanceof \PhpParser\Node\Expr\MethodCall || $caller instanceof \PhpParser\Node\Expr\StaticCall) { return $this->isFoundByRefParam($caller); } } if ($parent instanceof \PhpParser\Node\Expr\ArrayDimFetch) { return !$this->readWritePropertyAnalyzer->isRead($propertyFetch); } return \false; } /** * @param \PhpParser\Node\Expr\MethodCall|\PhpParser\Node\Expr\StaticCall $node */ private function isFoundByRefParam($node) : bool { $functionLikeReflection = $this->reflectionResolver->resolveFunctionLikeReflectionFromCall($node); if ($functionLikeReflection === null) { return \false; } $parametersAcceptor = \PHPStan\Reflection\ParametersAcceptorSelector::selectSingle($functionLikeReflection->getVariants()); foreach ($parametersAcceptor->getParameters() as $parameterReflection) { if ($parameterReflection->passedByReference()->yes()) { return \true; } } return \false; } }