parentClassMethodTypeOverrideGuard = $parentClassMethodTypeOverrideGuard; $this->astResolver = $astResolver; $this->betterStandardPrinter = $betterStandardPrinter; $this->betterNodeFinder = $betterNodeFinder; $this->reflectionResolver = $reflectionResolver; } public function provideMinPhpVersion() : int { return PhpVersionFeature::FATAL_ERROR_ON_INCOMPATIBLE_METHOD_SIGNATURE; } public function getRuleDefinition() : RuleDefinition { return new RuleDefinition('Add missing parameter based on parent class method', [new CodeSample(<<<'CODE_SAMPLE' class A { public function execute($foo) { } } class B extends A{ public function execute() { } } CODE_SAMPLE , <<<'CODE_SAMPLE' class A { public function execute($foo) { } } class B extends A{ public function execute($foo) { } } CODE_SAMPLE )]); } /** * @return array> */ public function getNodeTypes() : array { return [ClassMethod::class]; } /** * @param ClassMethod $node */ public function refactor(Node $node) : ?Node { if ($this->nodeNameResolver->isName($node, MethodName::CONSTRUCT)) { return null; } $parentMethodReflection = $this->parentClassMethodTypeOverrideGuard->getParentClassMethod($node); if (!$parentMethodReflection instanceof MethodReflection) { return null; } if ($parentMethodReflection->isPrivate()) { return null; } $currentClassReflection = $this->reflectionResolver->resolveClassReflection($node); $isPDO = $currentClassReflection instanceof ClassReflection && $currentClassReflection->isSubclassOf('PDO'); // It relies on phpstorm stubs that define 2 kind of query method for both php 7.4 and php 8.0 // @see https://github.com/JetBrains/phpstorm-stubs/blob/e2e898a29929d2f520fe95bdb2109d8fa895ba4a/PDO/PDO.php#L1096-L1126 if ($isPDO && $parentMethodReflection->getName() === 'query') { return null; } $parentClassMethod = $this->astResolver->resolveClassMethodFromMethodReflection($parentMethodReflection); if (!$parentClassMethod instanceof ClassMethod) { return null; } $currentClassMethodParams = $node->getParams(); $parentClassMethodParams = $parentClassMethod->getParams(); $countCurrentClassMethodParams = \count($currentClassMethodParams); $countParentClassMethodParams = \count($parentClassMethodParams); if ($countCurrentClassMethodParams === $countParentClassMethodParams) { return null; } if ($countCurrentClassMethodParams < $countParentClassMethodParams) { return $this->processReplaceClassMethodParams($node, $parentClassMethod, $currentClassMethodParams, $parentClassMethodParams); } return $this->processAddNullDefaultParam($node, $currentClassMethodParams, $parentClassMethodParams); } /** * @param Param[] $currentClassMethodParams * @param Param[] $parentClassMethodParams */ private function processAddNullDefaultParam(ClassMethod $classMethod, array $currentClassMethodParams, array $parentClassMethodParams) : ?ClassMethod { $hasChanged = \false; foreach ($currentClassMethodParams as $key => $currentClassMethodParam) { if (isset($parentClassMethodParams[$key])) { continue; } if ($currentClassMethodParam->default instanceof Expr) { continue; } if ($currentClassMethodParam->variadic) { continue; } $currentClassMethodParams[$key]->default = $this->nodeFactory->createNull(); $hasChanged = \true; } if (!$hasChanged) { return null; } return $classMethod; } /** * @param array $currentClassMethodParams * @param array $parentClassMethodParams */ private function processReplaceClassMethodParams(ClassMethod $node, ClassMethod $parentClassMethod, array $currentClassMethodParams, array $parentClassMethodParams) : ?ClassMethod { $originalParams = $node->params; foreach ($parentClassMethodParams as $key => $parentClassMethodParam) { if (isset($currentClassMethodParams[$key])) { $currentParamName = $this->nodeNameResolver->getName($currentClassMethodParams[$key]); $collectParamNamesNextKey = $this->collectParamNamesNextKey($parentClassMethod, $key); if (\in_array($currentParamName, $collectParamNamesNextKey, \true)) { $node->params = $originalParams; return null; } continue; } $isUsedInStmts = (bool) $this->betterNodeFinder->findFirstInFunctionLikeScoped($node, function (Node $subNode) use($parentClassMethodParam) : bool { if (!$subNode instanceof Variable) { return \false; } return $this->nodeComparator->areNodesEqual($subNode, $parentClassMethodParam->var); }); if ($isUsedInStmts) { $node->params = $originalParams; return null; } $paramDefault = $parentClassMethodParam->default; if ($paramDefault instanceof Expr) { $paramDefault = $this->nodeFactory->createReprintedExpr($paramDefault); } $paramName = $this->nodeNameResolver->getName($parentClassMethodParam); $paramType = $this->resolveParamType($parentClassMethodParam); $node->params[$key] = new Param(new Variable($paramName), $paramDefault, $paramType, $parentClassMethodParam->byRef, $parentClassMethodParam->variadic, [], $parentClassMethodParam->flags); if ($parentClassMethodParam->attrGroups !== []) { $attrGroupsAsComment = $this->betterStandardPrinter->print($parentClassMethodParam->attrGroups); $node->params[$key]->setAttribute(AttributeKey::COMMENTS, [new Comment($attrGroupsAsComment)]); } } return $node; } /** * @return null|\PhpParser\Node\Identifier|\PhpParser\Node\Name|\PhpParser\Node\ComplexType */ private function resolveParamType(Param $param) { if ($param->type === null) { return null; } $paramType = $param->type; $paramType->setAttribute(AttributeKey::ORIGINAL_NODE, null); return $paramType; } /** * @return string[] */ private function collectParamNamesNextKey(ClassMethod $classMethod, int $key) : array { $paramNames = []; foreach ($classMethod->params as $paramKey => $param) { if ($paramKey > $key) { $paramNames[] = $this->nodeNameResolver->getName($param); } } return $paramNames; } }