2021-10-07 16:13:11 +00:00
< ? php
declare ( strict_types = 1 );
2022-06-06 17:12:56 +00:00
namespace Rector\CodeQuality\Rector\PropertyFetch ;
2021-10-07 16:13:11 +00:00
2022-06-06 17:12:56 +00:00
use PhpParser\Node ;
use PhpParser\Node\Expr ;
use PhpParser\Node\Expr\Assign ;
use PhpParser\Node\Expr\MethodCall ;
use PhpParser\Node\Expr\PropertyFetch ;
use PHPStan\Analyser\Scope ;
use PHPStan\Reflection\ParametersAcceptorSelector ;
use PHPStan\Reflection\ResolvedMethodReflection ;
use PHPStan\Type\ObjectType ;
use Rector\Core\Rector\AbstractScopeAwareRector ;
use Rector\Core\ValueObject\MethodName ;
use Rector\NodeTypeResolver\Node\AttributeKey ;
2022-06-07 09:18:30 +00:00
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample ;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition ;
2021-10-07 16:13:11 +00:00
/**
* @ changelog https :// github . com / symplify / phpstan - rules / blob / main / docs / rules_overview . md #explicitmethodcallovermagicgetsetrule
*
* @ inspired by \Rector\Transform\Rector\Assign\GetAndSetToMethodCallRector
* @ phpstan - rule https :// github . com / symplify / phpstan - rules / blob / main / src / Rules / Explicit / ExplicitMethodCallOverMagicGetSetRule . php
*
* @ see \Rector\Tests\CodeQuality\Rector\PropertyFetch\ExplicitMethodCallOverMagicGetSetRector\ExplicitMethodCallOverMagicGetSetRectorTest
*/
2022-06-07 08:22:29 +00:00
final class ExplicitMethodCallOverMagicGetSetRector extends AbstractScopeAwareRector
2021-10-07 16:13:11 +00:00
{
2022-06-07 08:22:29 +00:00
public function getRuleDefinition () : RuleDefinition
2021-10-07 16:13:11 +00:00
{
2022-06-07 08:22:29 +00:00
return new RuleDefinition ( 'Replace magic property fetch using __get() and __set() with existing method get*()/set*() calls' , [ new CodeSample ( <<< 'CODE_SAMPLE'
2021-10-07 16:13:11 +00:00
class MagicCallsObject
{
// adds magic __get() and __set() methods
use \Nette\SmartObject ;
private $name ;
public function getName ()
{
return $this -> name ;
}
}
class SomeClass
{
public function run ( MagicObject $magicObject )
{
return $magicObject -> name ;
}
}
CODE_SAMPLE
, <<< 'CODE_SAMPLE'
class MagicCallsObject
{
// adds magic __get() and __set() methods
use \Nette\SmartObject ;
private $name ;
public function getName ()
{
return $this -> name ;
}
}
class SomeClass
{
public function run ( MagicObject $magicObject )
{
return $magicObject -> getName ();
}
}
CODE_SAMPLE
)]);
}
/**
* @ return array < class - string < Node >>
*/
public function getNodeTypes () : array
{
2022-06-07 08:22:29 +00:00
return [ PropertyFetch :: class , Assign :: class ];
2021-10-07 16:13:11 +00:00
}
/**
2021-12-10 10:22:23 +00:00
* @ param PropertyFetch | Assign $node
2021-10-07 16:13:11 +00:00
*/
2022-06-07 08:22:29 +00:00
public function refactorWithScope ( Node $node , Scope $scope ) : ? Node
2021-10-07 16:13:11 +00:00
{
2022-06-07 08:22:29 +00:00
if ( $node instanceof Assign ) {
if ( $node -> var instanceof PropertyFetch ) {
2022-05-12 09:11:03 +00:00
return $this -> refactorMagicSet ( $node -> expr , $node -> var , $scope );
2021-10-07 16:13:11 +00:00
}
return null ;
}
2021-10-14 14:35:01 +00:00
if ( $this -> shouldSkipPropertyFetch ( $node )) {
return null ;
}
2022-07-15 23:16:52 +00:00
return $this -> refactorPropertyFetch ( $node , $scope );
2021-10-07 16:13:11 +00:00
}
2022-05-12 06:13:58 +00:00
/**
* @ return string []
*/
2022-12-23 17:10:25 +00:00
private function resolvePossibleGetMethodNames ( string $propertyName ) : array
2022-05-12 06:13:58 +00:00
{
2022-12-23 17:10:25 +00:00
$upperPropertyName = \ucfirst ( $propertyName );
return [ 'get' . $upperPropertyName , 'has' . $upperPropertyName , 'is' . $upperPropertyName ];
2022-05-12 06:13:58 +00:00
}
2022-06-07 08:22:29 +00:00
private function shouldSkipPropertyFetch ( PropertyFetch $propertyFetch ) : bool
2021-10-14 14:35:01 +00:00
{
2022-08-09 13:39:17 +00:00
$parentNode = $propertyFetch -> getAttribute ( AttributeKey :: PARENT_NODE );
if ( ! $parentNode instanceof Assign ) {
2021-10-14 14:35:01 +00:00
return \false ;
}
2022-08-09 13:39:17 +00:00
return $parentNode -> var === $propertyFetch ;
2021-10-14 14:35:01 +00:00
}
2022-08-11 14:29:35 +00:00
private function refactorPropertyFetch ( PropertyFetch $propertyFetch , Scope $scope ) : ? \PhpParser\Node\Expr\MethodCall
2021-10-07 16:13:11 +00:00
{
2021-10-07 17:46:41 +00:00
$callerType = $this -> getType ( $propertyFetch -> var );
2022-06-07 08:22:29 +00:00
if ( ! $callerType instanceof ObjectType ) {
2021-10-07 16:13:11 +00:00
return null ;
}
// has magic methods?
2022-06-07 08:22:29 +00:00
if ( ! $callerType -> hasMethod ( MethodName :: __GET ) -> yes ()) {
2021-10-07 16:13:11 +00:00
return null ;
}
$propertyName = $this -> getName ( $propertyFetch -> name );
if ( $propertyName === null ) {
return null ;
}
2022-07-28 14:51:08 +00:00
if ( ! $callerType -> hasProperty ( $propertyName ) -> yes ()) {
return null ;
}
2022-07-18 15:25:27 +00:00
$propertyReflection = $callerType -> getProperty ( $propertyName , $scope );
$propertyType = $propertyReflection -> getReadableType ();
2022-05-12 06:13:58 +00:00
$possibleGetterMethodNames = $this -> resolvePossibleGetMethodNames ( $propertyName );
2021-10-07 16:13:11 +00:00
foreach ( $possibleGetterMethodNames as $possibleGetterMethodName ) {
if ( ! $callerType -> hasMethod ( $possibleGetterMethodName ) -> yes ()) {
continue ;
}
2022-07-15 23:16:52 +00:00
$methodReflection = $callerType -> getMethod ( $possibleGetterMethodName , $scope );
$variant = ParametersAcceptorSelector :: selectSingle ( $methodReflection -> getVariants ());
$returnType = $variant -> getReturnType ();
if ( ! $propertyType -> isSuperTypeOf ( $returnType ) -> yes ()) {
continue ;
}
2021-10-07 16:13:11 +00:00
return $this -> nodeFactory -> createMethodCall ( $propertyFetch -> var , $possibleGetterMethodName );
}
return null ;
}
2022-08-11 14:29:35 +00:00
private function refactorMagicSet ( Expr $expr , PropertyFetch $propertyFetch , Scope $scope ) : ? \PhpParser\Node\Expr\MethodCall
2021-10-07 16:13:11 +00:00
{
2021-10-07 17:46:41 +00:00
$propertyCallerType = $this -> getType ( $propertyFetch -> var );
2022-06-07 08:22:29 +00:00
if ( ! $propertyCallerType instanceof ObjectType ) {
2021-10-07 16:13:11 +00:00
return null ;
}
2022-06-07 08:22:29 +00:00
if ( ! $propertyCallerType -> hasMethod ( MethodName :: __SET ) -> yes ()) {
2021-10-07 16:13:11 +00:00
return null ;
}
$propertyName = $this -> getName ( $propertyFetch -> name );
if ( $propertyName === null ) {
return null ;
}
$setterMethodName = 'set' . \ucfirst ( $propertyName );
if ( ! $propertyCallerType -> hasMethod ( $setterMethodName ) -> yes ()) {
return null ;
}
2022-05-12 09:11:03 +00:00
if ( $this -> hasNoParamOrVariadic ( $propertyCallerType , $setterMethodName , $scope )) {
2022-02-05 11:36:15 +00:00
return null ;
2022-02-03 09:56:15 +00:00
}
2021-10-07 16:13:11 +00:00
return $this -> nodeFactory -> createMethodCall ( $propertyFetch -> var , $setterMethodName , [ $expr ]);
}
2022-06-07 08:22:29 +00:00
private function hasNoParamOrVariadic ( ObjectType $objectType , string $setterMethodName , Scope $scope ) : bool
2022-02-05 11:36:15 +00:00
{
2022-11-04 07:12:49 +00:00
$extendedMethodReflection = $objectType -> getMethod ( $setterMethodName , $scope );
if ( ! $extendedMethodReflection instanceof ResolvedMethodReflection ) {
2022-02-05 11:36:15 +00:00
return \false ;
}
2022-11-04 07:12:49 +00:00
$parametersAcceptor = ParametersAcceptorSelector :: selectSingle ( $extendedMethodReflection -> getVariants ());
2022-02-05 11:36:15 +00:00
$parameters = $parametersAcceptor -> getParameters ();
if ( \count ( $parameters ) !== 1 ) {
return \true ;
}
return $parameters [ 0 ] -> isVariadic ();
}
2021-10-07 16:13:11 +00:00
}