private function checkParametersAcceptor(ParametersAcceptor $parametersAcceptor, string $parameterMessage, string $returnMessage) : array { $errors = []; foreach ($parametersAcceptor->getParameters() as $parameter) { $type = $parameter->getType(); if ($type->getClass() !== null && !$this->broker->hasClass($type->getClass())) { $errors[] = sprintf($parameterMessage, $parameter->getName(), $type->getClass()); } elseif ($type instanceof ArrayType) { $nestedItemType = $type->getNestedItemType(); if ($nestedItemType->getItemType()->getClass() !== null && !$this->broker->hasClass($nestedItemType->getItemType()->getClass())) { $errors[] = sprintf($parameterMessage, $parameter->getName(), $type->describe()); } } } $returnType = $parametersAcceptor->getReturnType(); if ($returnType->getClass() !== null && !$this->broker->hasClass($returnType->getClass())) { $errors[] = sprintf($returnMessage, $returnType->getClass()); } elseif ($returnType instanceof ArrayType) { $nestedItemType = $returnType->getNestedItemType(); if ($nestedItemType->getItemType()->getClass() !== null && !$this->broker->hasClass($nestedItemType->getItemType()->getClass())) { $errors[] = sprintf($returnMessage, $returnType->describe()); } } return $errors; }
/** * @param \PhpParser\Node\Expr\New_ $node * @param \PHPStan\Analyser\Scope $scope * @return string[] */ public function processNode(Node $node, Scope $scope) : array { if (!$node->class instanceof \PhpParser\Node\Name) { return []; } $class = (string) $node->class; if ($class === 'static') { return []; } if ($class === 'self') { $class = $scope->getClass(); if ($class === null) { return []; } } if (!$this->broker->hasClass($class)) { return [sprintf('Instantiated class %s not found.', $class)]; } $classReflection = $this->broker->getClass($class); if ($classReflection->isInterface()) { return [sprintf('Cannot instantiate interface %s.', $classReflection->getName())]; } if ($classReflection->isAbstract()) { return [sprintf('Instantiated class %s is abstract.', $classReflection->getName())]; } if (!$classReflection->hasMethod('__construct') && !$classReflection->hasMethod($class)) { if (count($node->args) > 0) { return [sprintf('Class %s does not have a constructor and must be instantiated without any parameters.', $classReflection->getName())]; } return []; } return $this->check->check($classReflection->hasMethod('__construct') ? $classReflection->getMethod('__construct') : $classReflection->getMethod($class), $node, ['Class ' . $classReflection->getName() . ' constructor invoked with %d parameter, %d required.', 'Class ' . $classReflection->getName() . ' constructor invoked with %d parameters, %d required.', 'Class ' . $classReflection->getName() . ' constructor invoked with %d parameter, at least %d required.', 'Class ' . $classReflection->getName() . ' constructor invoked with %d parameters, at least %d required.', 'Class ' . $classReflection->getName() . ' constructor invoked with %d parameter, %d-%d required.', 'Class ' . $classReflection->getName() . ' constructor invoked with %d parameters, %d-%d required.']); }
/** * @param \PhpParser\Node\Expr\StaticCall $node * @param \PHPStan\Analyser\Scope $scope * @return string[] */ public function processNode(Node $node, Scope $scope) : array { if (!is_string($node->name)) { return []; } $name = $node->name; $currentClass = $scope->getClass(); if ($currentClass === null) { return []; } $currentClassReflection = $this->broker->getClass($currentClass); if (!$node->class instanceof Name) { return []; } $class = (string) $node->class; if ($class === 'self' || $class === 'static') { $class = $currentClass; } if ($class === 'parent') { if ($currentClassReflection->getParentClass() === false) { return [sprintf('%s::%s() calls to parent::%s() but %s does not extend any class.', $currentClass, $scope->getFunctionName(), $name, $currentClass)]; } $currentMethodReflection = $currentClassReflection->getMethod($scope->getFunctionName()); if (!$currentMethodReflection->isStatic()) { if ($name === '__construct' && $currentClassReflection->getParentClass()->hasMethod('__construct')) { return $this->check->check($currentClassReflection->getParentClass()->getMethod('__construct'), $node, ['Parent constructor invoked with %d parameter, %d required.', 'Parent constructor invoked with %d parameters, %d required.', 'Parent constructor invoked with %d parameter, at least %d required.', 'Parent constructor invoked with %d parameters, at least %d required.', 'Parent constructor invoked with %d parameter, %d-%d required.', 'Parent constructor invoked with %d parameters, %d-%d required.']); } return []; } $class = $currentClassReflection->getParentClass()->getName(); } if (!$this->broker->hasClass($class)) { return [sprintf('Call to static method %s() on an unknown class %s.', $name, $class)]; } $classReflection = $this->broker->getClass($class); if (!$classReflection->hasMethod($name)) { return [sprintf('Call to an undefined static method %s::%s().', $classReflection->getName(), $name)]; } $method = $classReflection->getMethod($name); if (!$method->isStatic()) { return [sprintf('Static call to instance method %s::%s().', $method->getDeclaringClass()->getName(), $method->getName())]; } if (!$scope->canCallMethod($method)) { return [sprintf('Call to %s static method %s() of class %s.', $method->isPrivate() ? 'private' : 'protected', $method->getName(), $method->getDeclaringClass()->getName())]; } $methodName = $method->getDeclaringClass()->getName() . '::' . $method->getName() . '()'; $errors = $this->check->check($method, $node, ['Static method ' . $methodName . ' invoked with %d parameter, %d required.', 'Static method ' . $methodName . ' invoked with %d parameters, %d required.', 'Static method ' . $methodName . ' invoked with %d parameter, at least %d required.', 'Static method ' . $methodName . ' invoked with %d parameters, at least %d required.', 'Static method ' . $methodName . ' invoked with %d parameter, %d-%d required.', 'Static method ' . $methodName . ' invoked with %d parameters, %d-%d required.']); if ($method->getName() !== $name) { $errors[] = sprintf('Call to static method %s with incorrect case: %s', $methodName, $name); } return $errors; }
/** * @param \PhpParser\Node\Expr\ClassConstFetch $node * @param \PHPStan\Analyser\Scope $scope * @return string[] */ public function processNode(Node $node, Scope $scope) : array { $class = $node->class; if ($class instanceof \PhpParser\Node\Name) { $className = (string) $class; } else { $classType = $scope->getType($class); if ($classType->getClass() !== null) { $className = $classType->getClass(); } else { return []; } } if ($className === 'self' || $className === 'static') { if ($scope->getClass() === null && !$scope->isInAnonymousClass()) { return [sprintf('Using %s outside of class scope.', $className)]; } if ($className === 'static') { return []; } if ($className === 'self') { $className = $scope->getClass(); } } $constantName = $node->name; if ($scope->getClass() !== null && $className === 'parent') { $currentClassReflection = $this->broker->getClass($scope->getClass()); if ($currentClassReflection->getParentClass() === false) { return [sprintf('Access to parent::%s but %s does not extend any class.', $constantName, $scope->getClass())]; } $className = $currentClassReflection->getParentClass()->getName(); } if (!$this->broker->hasClass($className)) { return [sprintf('Class %s not found.', $className)]; } if ($constantName === 'class') { return []; } $classReflection = $this->broker->getClass($className); if (!$classReflection->hasConstant($constantName)) { return [sprintf('Access to undefined constant %s::%s.', $classReflection->getName(), $constantName)]; } $constantReflection = $classReflection->getConstant($constantName); if (!$scope->canAccessConstant($constantReflection)) { return [sprintf('Cannot access constant %s::%s from current scope.', $constantReflection->getDeclaringClass()->getName(), $constantName)]; } return []; }
/** * @param \PhpParser\Node\Stmt\Catch_ $node * @param \PHPStan\Analyser\Scope $scope * @return string[] */ public function processNode(Node $node, Scope $scope) : array { $classes = $node->types; $errors = []; foreach ($classes as $className) { $class = (string) $className; if (!$this->broker->hasClass($class)) { $errors[] = sprintf('Catched class %s not found.', $class); continue; } $classReflection = $this->broker->getClass($class); if (!$classReflection->isInterface() && !$classReflection->getNativeReflection()->implementsInterface(\Throwable::class)) { $errors[] = sprintf('Catched class %s is not an exception.', $class); } } return $errors; }
/** * @param \PhpParser\Node\Expr\StaticPropertyFetch $node * @param \PHPStan\Analyser\Scope $scope * @return string[] */ public function processNode(Node $node, Scope $scope) : array { if (!is_string($node->name) || !$node->class instanceof Node\Name) { return []; } $name = $node->name; $currentClass = $scope->getClass(); if ($currentClass === null) { return []; } $currentClassReflection = $this->broker->getClass($currentClass); $class = (string) $node->class; if ($class === 'self' || $class === 'static') { $class = $currentClass; } if ($class === 'parent') { if ($currentClassReflection->getParentClass() === false) { return [sprintf('%s::%s() accesses parent::$%s but %s does not extend any class.', $currentClass, $scope->getFunctionName(), $name, $currentClass)]; } $currentMethodReflection = $currentClassReflection->getMethod($scope->getFunctionName()); if (!$currentMethodReflection->isStatic()) { // calling parent::method() from instance method return []; } $class = $currentClassReflection->getParentClass()->getName(); } if (!$this->broker->hasClass($class)) { return [sprintf('Access to static property $%s on an unknown class %s.', $name, $class)]; } $classReflection = $this->broker->getClass($class); if (!$classReflection->hasProperty($name)) { return [sprintf('Access to an undefined static property %s::$%s.', $classReflection->getName(), $name)]; } $property = $classReflection->getProperty($name, $scope); if (!$property->isStatic()) { return [sprintf('Static access to instance property %s::$%s.', $property->getDeclaringClass()->getName(), $name)]; } if (!$scope->canAccessProperty($property)) { return [sprintf('Cannot access property %s::$%s from current scope.', $property->getDeclaringClass()->getName(), $name)]; } return []; }
/** * @param \PhpParser\Node\Expr\MethodCall $node * @param \PHPStan\Analyser\Scope $scope * @return string[] */ public function processNode(Node $node, Scope $scope) : array { if (!is_string($node->name)) { return []; } if ($this->checkThisOnly && !$this->ruleLevelHelper->isThis($node->var)) { return []; } $type = $scope->getType($node->var); if (!$type->canCallMethods()) { return [sprintf('Cannot call method %s() on %s.', $node->name, $type->describe())]; } $methodClass = $type->getClass(); if ($methodClass === null) { return []; } $name = (string) $node->name; if (!$this->broker->hasClass($methodClass)) { return [sprintf('Call to method %s() on an unknown class %s.', $name, $methodClass)]; } $methodClassReflection = $this->broker->getClass($methodClass); if (!$methodClassReflection->hasMethod($name)) { $parentClassReflection = $methodClassReflection->getParentClass(); while ($parentClassReflection !== false) { if ($parentClassReflection->hasMethod($name)) { return [sprintf('Call to private method %s() of parent class %s.', $parentClassReflection->getMethod($name)->getName(), $parentClassReflection->getName())]; } $parentClassReflection = $parentClassReflection->getParentClass(); } return [sprintf('Call to an undefined method %s::%s().', $methodClassReflection->getName(), $name)]; } $methodReflection = $methodClassReflection->getMethod($name); $messagesMethodName = $methodReflection->getDeclaringClass()->getName() . '::' . $methodReflection->getName() . '()'; if (!$scope->canCallMethod($methodReflection)) { return [sprintf('Cannot call method %s from current scope.', $messagesMethodName)]; } $errors = $this->check->check($methodReflection, $node, ['Method ' . $messagesMethodName . ' invoked with %d parameter, %d required.', 'Method ' . $messagesMethodName . ' invoked with %d parameters, %d required.', 'Method ' . $messagesMethodName . ' invoked with %d parameter, at least %d required.', 'Method ' . $messagesMethodName . ' invoked with %d parameters, at least %d required.', 'Method ' . $messagesMethodName . ' invoked with %d parameter, %d-%d required.', 'Method ' . $messagesMethodName . ' invoked with %d parameters, %d-%d required.']); if ($methodReflection->getName() !== $name) { $errors[] = sprintf('Call to method %s with incorrect case: %s', $messagesMethodName, $name); } return $errors; }
/** * @param \PhpParser\Node\Stmt\Property $node * @param \PHPStan\Analyser\Scope $scope * @return string[] */ public function processNode(Node $node, Scope $scope) : array { if ($scope->getClass() === null || !$this->broker->hasClass($scope->getClass())) { return []; } $classReflection = $this->broker->getClass($scope->getClass()); $errors = []; foreach ($node->props as $property) { if ($property->default === null) { continue; } $propertyReflection = $classReflection->getProperty($property->name); $propertyType = $propertyReflection->getType(); $defaultValueType = $scope->getType($property->default); if ($propertyType->accepts($defaultValueType)) { continue; } $errors[] = sprintf('%s %s::$%s (%s) does not accept default value of type %s.', $node->isStatic() ? 'Static property' : 'Property', $scope->getClass(), $property->name, $propertyType->describe(), $defaultValueType->describe()); } return $errors; }
/** * @param \PhpParser\Node\Expr\PropertyFetch $node * @param \PHPStan\Analyser\Scope $scope * @return string[] */ public function processNode(\PhpParser\Node $node, Scope $scope) : array { if (!is_string($node->name)) { return []; } if ($this->checkThisOnly && !$this->ruleLevelHelper->isThis($node->var)) { return []; } $type = $scope->getType($node->var); if (!$type->canAccessProperties()) { return [sprintf('Cannot access property $%s on %s.', $node->name, $type->describe())]; } $propertyClass = $type->getClass(); if ($propertyClass === null) { return []; } $name = (string) $node->name; if (!$this->broker->hasClass($propertyClass)) { return [sprintf('Access to property $%s on an unknown class %s.', $name, $propertyClass)]; } $propertyClassReflection = $this->broker->getClass($propertyClass); if (!$propertyClassReflection->hasProperty($name)) { if ($scope->isSpecified($node)) { return []; } $parentClassReflection = $propertyClassReflection->getParentClass(); while ($parentClassReflection !== false) { if ($parentClassReflection->hasProperty($name)) { return [sprintf('Access to private property $%s of parent class %s.', $name, $parentClassReflection->getName())]; } $parentClassReflection = $parentClassReflection->getParentClass(); } return [sprintf('Access to an undefined property %s::$%s.', $propertyClass, $name)]; } $propertyReflection = $propertyClassReflection->getProperty($name, $scope); if (!$scope->canAccessProperty($propertyReflection)) { return [sprintf('Cannot access property %s::$%s from current scope.', $propertyReflection->getDeclaringClass()->getName(), $name)]; } return []; }
/** * @param \PhpParser\Node\Stmt\PropertyProperty $node * @param \PHPStan\Analyser\Scope $scope * @return string[] */ public function processNode(Node $node, Scope $scope) : array { $className = $scope->getClass(); if ($className === null) { return []; } $classReflection = $this->broker->getClass($className); $propertyType = $classReflection->getProperty($node->name, $scope)->getType(); if ($propertyType instanceof ArrayType) { $nestedItemType = $propertyType->getNestedItemType(); if ($nestedItemType->getItemType()->getClass() !== null && !$this->broker->hasClass($nestedItemType->getItemType()->getClass())) { return [sprintf('Property %s::$%s has unknown class %s as its array type.', $className, $node->name, $propertyType->describe())]; } } if ($propertyType->getClass() === null) { return []; } if (!$this->broker->hasClass($propertyType->getClass())) { return [sprintf('Property %s::$%s has unknown class %s as its type.', $className, $node->name, $propertyType->getClass())]; } return []; }
/** * @param \PhpParser\Node\Expr\Instanceof_ $node * @param \PHPStan\Analyser\Scope $scope * @return string[] */ public function processNode(Node $node, Scope $scope) : array { $class = $node->class; if (!$class instanceof \PhpParser\Node\Name) { return []; } $name = (string) $class; if ($name === 'self' || $name === 'static') { if ($scope->getClass() === null && !$scope->isInAnonymousClass()) { return [sprintf('Using %s outside of class scope.', $name)]; } if ($name === 'static') { return []; } if ($name === 'self') { $name = $scope->getClass(); } } if (!$this->broker->hasClass($name)) { return [sprintf('Class %s not found.', $name)]; } return []; }
/** * @param \PhpParser\Node\Expr\ClassConstFetch $node * @param \PHPStan\Analyser\Scope $scope * @return string[] */ public function processNode(Node $node, Scope $scope) : array { $class = $node->class; if ($class instanceof \PhpParser\Node\Name) { $className = (string) $class; } else { $classType = $scope->getType($class); if ($classType->getClass() !== null) { $className = $classType->getClass(); } else { return []; } } if ($className === 'self' || $className === 'static') { if ($scope->getClass() === null && !$scope->isInAnonymousClass()) { return [sprintf('Using %s outside of class scope.', $className)]; } if ($className === 'static') { return []; } if ($className === 'self') { $className = $scope->getClass(); } } if (!$this->broker->hasClass($className)) { return [sprintf('Class %s not found.', $className)]; } $constantName = $node->name; if ($constantName === 'class') { return []; } $classReflection = $this->broker->getClass($className); if (!$classReflection->hasConstant($constantName)) { return [sprintf('Access to undefined constant %s::%s.', $className, $constantName)]; } return []; }
private function processTraitUse(Node\Stmt\TraitUse $node, Scope $classScope, \Closure $nodeCallback) { foreach ($node->traits as $trait) { $traitName = (string) $trait; if (!$this->broker->hasClass($traitName)) { continue; } $traitReflection = $this->broker->getClass($traitName); $fileName = $traitReflection->getNativeReflection()->getFileName(); $parserNodes = $this->parser->parseFile($fileName); $classScope = $classScope->changeAnalysedContextFile(sprintf('%s (in context of %s)', $fileName, $classScope->getClass() !== null ? sprintf('class %s', $classScope->getClass()) : 'anonymous class')); $this->processNodes($parserNodes, new Scope($this->broker, $this->printer, $fileName), function (\PhpParser\Node $node) use($traitName, $classScope, $nodeCallback) { if ($node instanceof Node\Stmt\Trait_ && $traitName === (string) $node->namespacedName) { $this->processNodes($node->stmts, $classScope, $nodeCallback); } }); } }
/** * @param \PhpParser\Node\Expr $functionCall * @param \PHPStan\Analyser\Scope $scope * @return null|\PHPStan\Reflection\ParameterReflection[] */ private function findParametersInFunctionCall(Expr $functionCall, Scope $scope) { if ($functionCall instanceof FuncCall && $functionCall->name instanceof Name) { if ($this->broker->hasFunction($functionCall->name, $scope)) { return $this->broker->getFunction($functionCall->name, $scope)->getParameters(); } } elseif ($functionCall instanceof MethodCall && is_string($functionCall->name)) { $type = $scope->getType($functionCall->var); if ($type->getClass() !== null && $this->broker->hasClass($type->getClass())) { $classReflection = $this->broker->getClass($type->getClass()); $methodName = $functionCall->name; if ($classReflection->hasMethod((string) $methodName)) { return $classReflection->getMethod((string) $methodName)->getParameters(); } } } return null; }
private function canAccessClassMember(ClassMemberReflection $classMemberReflection) : bool { if ($classMemberReflection->isPublic()) { return true; } $class = $this->inClosureBindScopeClass !== null ? $this->inClosureBindScopeClass : $this->getClass(); if ($class === null) { return false; } if (!$this->broker->hasClass($class)) { return false; } $classReflectionName = $classMemberReflection->getDeclaringClass()->getName(); if ($classMemberReflection->isPrivate()) { return $class === $classReflectionName; } $currentClassReflection = $this->broker->getClass($class); // protected return $currentClassReflection->getName() === $classReflectionName || $currentClassReflection->isSubclassOf($classReflectionName); }