Skip to content

Commit

Permalink
Generic/UnusedFunctionParameter: ignore magic methods
Browse files Browse the repository at this point in the history
The function signature of magic methods is dictated by PHP and unused parameters cannot be removed, which means that the warnings for these can never be resolved, only ignored via annotations.

This commit fixes this by checking whether a function is a magic method and if so, bowing out.

Includes unit tests.

Note: while not all magic methods take arguments, I'm still including the full list of magic methods in the property as the other magic methods can be ignored anyway (no arguments).
  • Loading branch information
jrfnl committed Nov 19, 2022
1 parent 9f297f0 commit ddf4dbb
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,31 @@ class UnusedFunctionParameterSniff implements Sniff
*/
public $ignoreTypeHints = [];

/**
* A list of all PHP magic methods.
*
* @var array
*/
private $magicMethods = [
'__construct' => true,
'__destruct' => true,
'__call' => true,
'__callstatic' => true,
'__get' => true,
'__set' => true,
'__isset' => true,
'__unset' => true,
'__sleep' => true,
'__wakeup' => true,
'__serialize' => true,
'__unserialize' => true,
'__tostring' => true,
'__invoke' => true,
'__set_state' => true,
'__clone' => true,
'__debuginfo' => true,
];


/**
* Returns an array of tokens this test wants to listen for.
Expand Down Expand Up @@ -71,8 +96,18 @@ public function process(File $phpcsFile, $stackPtr)
$extends = false;

if ($token['code'] === T_FUNCTION) {
$classPtr = $phpcsFile->getCondition($stackPtr, T_CLASS);
$classPtr = $phpcsFile->getCondition($stackPtr, T_CLASS);
if ($classPtr !== false) {
// Check for magic methods and ignore these as the method signature cannot be changed.
$methodName = $phpcsFile->getDeclarationName($stackPtr);
if (empty($methodName) === false) {
$methodNameLc = strtolower($methodName);
if (isset($this->magicMethods[$methodNameLc]) === true) {
return;
}
}

// Check for extends/implements and adjust the error code when found.
$implements = $phpcsFile->findImplementedInterfaceNames($classPtr);
$extends = $phpcsFile->findExtendedClassName($classPtr);
if ($extends !== false) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,50 @@ class MyExtendedClass implements SomeInterface {
$fn = fn($c, $d) => $c[2];
}
}


/**
* Magic methods must match the function signature dictated by PHP.
* Flagging unused parameters leads to notices which cannot be solved.
*/
class MagicMethodsWithParams {
public function __set(string $name, mixed $value) {
// Forbid dynamic properties & overloading inaccessible properties.
throw new RuntimeException('Forbidden');
}

public function __get(string $name) {
throw new RuntimeException('Forbidden');
}

public function __isset(string $name) {
throw new RuntimeException('Forbidden');
}

public function __unset(string $name) {
throw new RuntimeException('Forbidden');
}

public function __unserialize( array $data ) {
// Prevent unserializing from a stored representation of the object for security reasons.
$this->instance = new self();
}

public static function __set_state(array $properties) {
return new self();
}

public function __call(string $name, array $arguments) {
if (method_exists($this, $name)) {
// None of the methods which can be called in this class take arguments, so not passing them.
return $this->$name();
}
}

public static function __callStatic(string $name, array $arguments) {
if (method_exists($this, $name)) {
// None of the methods which can be called in this class take arguments, so not passing them.
return self::$name();
}
}
}

0 comments on commit ddf4dbb

Please sign in to comment.