Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
namespace Rector\CodingStyle\Rector\ClassMethod;

use PhpParser\Node;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Return_;
use PHPStan\Analyser\Scope;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\Rector\AbstractRector;
Expand All @@ -12,6 +14,8 @@
use ReflectionMethod;

/**
* @see https://3v4l.org/RFYmn
*
* @see \Rector\CodingStyle\Tests\Rector\ClassMethod\MakeInheritedMethodVisibilitySameAsParentRector\MakeInheritedMethodVisibilitySameAsParentRectorTest
*/
final class MakeInheritedMethodVisibilitySameAsParentRector extends AbstractRector
Expand Down Expand Up @@ -93,6 +97,10 @@ public function refactor(Node $node): ?Node
return null;
}

if ($this->isConstructorWithStaticFactory($node, $methodName)) {
return null;
}

$this->changeClassMethodVisibilityBasedOnReflectionMethod($node, $parentReflectionMethod);

return $node;
Expand Down Expand Up @@ -139,4 +147,72 @@ private function changeClassMethodVisibilityBasedOnReflectionMethod(
return;
}
}

/**
* Parent constructor visibility override is allowed only since PHP 7.2+
* @see https://3v4l.org/RFYmn
*/
private function isConstructorWithStaticFactory(ClassMethod $classMethod, string $methodName): bool
{
if (! $this->isAtLeastPhpVersion('7.2')) {
return false;
}

if ($methodName !== '__construct') {
return false;
}

/** @var Node\Stmt\Class_|null $class */
$class = $classMethod->getAttribute(AttributeKey::CLASS_NODE);
if ($class === null) {
return false;
}

foreach ($class->getMethods() as $iteratedClassMethod) {
if (! $iteratedClassMethod->isPublic()) {
continue;
}

if (! $iteratedClassMethod->isStatic()) {
continue;
}

$isStaticSelfFactory = $this->isStaticSelfFactory($iteratedClassMethod);

if ($isStaticSelfFactory === false) {
continue;
}

return true;
}

return false;
}

/**
* Looks for:
* public static someMethod() { return new self(); }
*/
private function isStaticSelfFactory(ClassMethod $classMethod): bool
{
if (! $classMethod->isPublic()) {
return false;
}

if (! $classMethod->isStatic()) {
return false;
}

return (bool) $this->betterNodeFinder->findFirst($classMethod, function (Node $node): bool {
if (! $node instanceof Return_) {
return false;
}

if (! $node->expr instanceof New_) {
return false;
}

return $this->isName($node->expr->class, 'self');
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Rector\CodingStyle\Tests\Rector\ClassMethod\MakeInheritedMethodVisibilitySameAsParentRector\Fixture;

class SkipStaticCtor extends ParentWithPublicConstructor
{
protected function __construct()
{
}

public static function create()
{
return new self();
}
}

class ParentWithPublicConstructor
{
public function __construct()
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php declare(strict_types=1);

namespace Rector\CodingStyle\Tests\Rector\ClassMethod\MakeInheritedMethodVisibilitySameAsParentRector;

use Iterator;
use Rector\CodingStyle\Rector\ClassMethod\MakeInheritedMethodVisibilitySameAsParentRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;

final class SkipParentConstructOverrideInPHP72Test extends AbstractRectorTestCase
{
/**
* @requires PHP >= 7.2
* @see https://phpunit.readthedocs.io/en/8.3/incomplete-and-skipped-tests.html#incomplete-and-skipped-tests-requires-tables-api
*
* @dataProvider provideDataForTest()
*/
public function test(string $file): void
{
$this->doTestFile($file);
}

public function provideDataForTest(): Iterator
{
yield [__DIR__ . '/Fixture/skip_static_ctor.php.inc'];
}

protected function getRectorClass(): string
{
return MakeInheritedMethodVisibilitySameAsParentRector::class;
}

protected function getPhpVersion(): string
{
return '7.2';
}
}