@ -20,130 +20,182 @@ trait MagicMethodDetector
$returnTypeUndeclared = $fnDef->returnTypeUndeclared;
$nameLower = strtolower($name);
$methodName = $this->class . "::{$name}";
$isStatic = (bool) ($methodDef->flags & \PhpParser\Modifiers::STATIC);
if ($nameLower == '__call' or $nameLower == '__callstatic') {
if (count($argInfoList) != 2) {
$this->fatalError($v, 'Method ' . $this->class . "::{$name}() must take exactly 2 arguments");
$mustBeStatic = ['__callstatic' => true, '__set_state' => true];
$mustNotBeStatic = [
'__construct' => true,
'__destruct' => true,
'__clone' => true,
'__call' => true,
'__get' => true,
'__set' => true,
'__isset' => true,
'__unset' => true,
'__sleep' => true,
'__wakeup' => true,
'__serialize' => true,
'__unserialize' => true,
'__debuginfo' => true,
'__tostring' => true,
'__invoke' => true,
];
if (isset($mustBeStatic[$nameLower]) & & !$isStatic) {
$this->fatalError($v, 'Method ' . $methodName . '() must be static');
}
if (isset($mustNotBeStatic[$nameLower]) & & $isStatic) {
$this->fatalError($v, 'Method ' . $methodName . '() cannot be static');
}
$mustBePublic = [
'__call' => true,
'__callstatic' => true,
'__get' => true,
'__set' => true,
'__isset' => true,
'__unset' => true,
'__sleep' => true,
'__wakeup' => true,
'__serialize' => true,
'__unserialize' => true,
'__debuginfo' => true,
'__tostring' => true,
'__invoke' => true,
'__set_state' => true,
];
if (isset($mustBePublic[$nameLower]) & & !($methodDef->flags & \PhpParser\Modifiers::PUBLIC)) {
$this->fatalError($v, 'Method ' . $methodName . '() must have public visibility');
}
$exactArgCount = [
'__destruct' => 0,
'__clone' => 0,
'__tostring' => 0,
'__sleep' => 0,
'__wakeup' => 0,
'__serialize' => 0,
'__debuginfo' => 0,
'__call' => 2,
'__callstatic' => 2,
'__set' => 2,
'__get' => 1,
'__isset' => 1,
'__unset' => 1,
'__set_state' => 1,
'__unserialize' => 1,
];
if (array_key_exists($nameLower, $exactArgCount) & & count($argInfoList) !== $exactArgCount[$nameLower]) {
$this->fatalError($v, 'Method ' . $methodName . '() must take exactly ' . $exactArgCount[$nameLower] . ' arguments');
}
if ($nameLower == '__call' or $nameLower == '__callstatic') {
if ($argInfoList[0]->undeclared) {
$argInfoList[0]->type = self::TYPE_STR;
} elseif ($argInfoList[0]->type !== self::TYPE_STR) {
$this->fatalError($v, 'Method ' . $this->class . "::{$name}() must take string as first argument");
$this->fatalError($v, 'Method ' . $methodName . '() must take string as first argument' );
}
if ($argInfoList[1]->undeclared) {
$argInfoList[1]->type = self::TYPE_ARRAY;
} elseif ($argInfoList[1]->type !== self::TYPE_ARRAY) {
$this->fatalError($v, 'Method ' . $this->class . "::{$name}() must take array as second argument");
$this->fatalError($v, 'Method ' . $methodName . '() must take array as second argument' );
}
} elseif ($nameLower == '__set') {
if (count($argInfoList) != 2) {
$this->fatalError($v, 'Method ' . $this->class . "::{$name}() must take exactly 2 arguments");
}
if ($argInfoList[0]->undeclared) {
$argInfoList[0]->type = self::TYPE_STR;
} elseif ($argInfoList[0]->type !== self::TYPE_STR) {
$this->fatalError($v, 'Method ' . $this->class . "::{$name}() must take string as first argument");
$this->fatalError($v, 'Method ' . $methodName . '() must take string as first argument' );
}
if ($returnTypeUndeclared) {
$fnDef->returnType = self::TYPE_VOID;
} elseif ($fnDef->returnType !== self::TYPE_VOID) {
$this->fatalError($v, 'Method ' . $this->class . "::{$name}() must return void");
$this->fatalError($v, 'Method ' . $methodName . '() must return void' );
}
} elseif ($nameLower == '__get') {
if (count($argInfoList) != 1) {
$this->fatalError($v, 'Method ' . $this->class . "::{$name}() must take exactly 1 argument");
if ($argInfoList[0]->undeclared) {
$argInfoList[0]->type = self::TYPE_STR;
} elseif ($argInfoList[0]->type !== self::TYPE_STR) {
$this->fatalError($v, 'Method ' . $methodName . '() must take string as argument');
}
} elseif ($nameLower == '__tostring') {
if ($returnTypeUndeclared) {
$fnDef->returnType = self::TYPE_STR;
} elseif ($fnDef->returnType !== self::TYPE_STR) {
$this->fatalError($v, 'Method ' . $this->class . "::{$name}() must return string");
$this->fatalError($v, 'Method ' . $methodName . '() must return string' );
}
} elseif ($nameLower == '__serialize') {
if ($returnTypeUndeclared) {
$fnDef->returnType = self::TYPE_ARRAY;
} elseif ($fnDef->returnType !== self::TYPE_ARRAY) {
$this->fatalError($v, 'Method ' . $this->class . "::{$name}() must return array");
$this->fatalError($v, 'Method ' . $methodName . '() must return array' );
}
} elseif ($nameLower == '__unserialize') {
if (count($argInfoList) != 1) {
$this->fatalError($v, 'Method ' . $this->class . "::{$name}() must take exactly 1 argument");
}
if ($argInfoList[0]->undeclared) {
$argInfoList[0]->type = self::TYPE_ARRAY;
} elseif ($argInfoList[0]->type !== self::TYPE_ARRAY) {
$this->fatalError($v, 'Method ' . $this->class . "::{$name}() must take array as argument");
$this->fatalError($v, 'Method ' . $methodName . '() must take array as argument' );
}
if ($returnTypeUndeclared) {
$fnDef->returnType = self::TYPE_VOID;
} elseif ($fnDef->returnType !== self::TYPE_VOID) {
$this->fatalError($v, 'Method ' . $this->class . "::{$name}() must return void");
$this->fatalError($v, 'Method ' . $methodName . '() must return void' );
}
} elseif ($nameLower == '__isset') {
if (count($argInfoList) != 1) {
$this->fatalError($v, 'Method ' . $this->class . "::{$name}() must take exactly 1 argument");
}
if ($argInfoList[0]->undeclared) {
$argInfoList[0]->type = self::TYPE_STR;
} elseif ($argInfoList[0]->type !== self::TYPE_STR) {
$this->fatalError($v, 'Method ' . $this->class . "::{$name}() must take string as argument" );
$this->fatalError($v, 'Method ' . $methodName . '() must take string as argument' );
}
if ($returnTypeUndeclared) {
$fnDef->returnType = self::TYPE_BOOL;
} elseif ($fnDef->returnType !== self::TYPE_BOOL) {
$this->fatalError($v, 'Method ' . $this->class . "::{$name}() must return bool" );
$this->fatalError($v, 'Method ' . $methodName . '() must return bool' );
}
} elseif ($nameLower == '__unset') {
if (count($argInfoList) != 1) {
$this->fatalError($v, 'Method ' . $this->class . "::{$name}() must take exactly 1 argument");
}
if ($argInfoList[0]->undeclared) {
$argInfoList[0]->type = self::TYPE_STR;
} elseif ($argInfoList[0]->type !== self::TYPE_STR) {
$this->fatalError($v, 'Method ' . $this->class . "::{$name}() must take string as argument" );
$this->fatalError($v, 'Method ' . $methodName . '() must take string as argument' );
}
if ($returnTypeUndeclared) {
$fnDef->returnType = self::TYPE_VOID;
} elseif ($fnDef->returnType !== self::TYPE_VOID) {
$this->fatalError($v, 'Method ' . $this->class . "::{$name}() must return void" );
$this->fatalError($v, 'Method ' . $methodName . '() must return void' );
}
} elseif ($nameLower == '__set_state') {
if (count($argInfoList) != 1) {
$this->fatalError($v, 'Method ' . $this->class . "::{$name}() must take exactly 1 argument");
}
if ($argInfoList[0]->undeclared) {
$argInfoList[0]->type = self::TYPE_ARRAY;
} elseif ($argInfoList[0]->type !== self::TYPE_ARRAY) {
$this->fatalError($v, 'Method ' . $this->class . "::{$name}() must take array as argument" );
$this->fatalError($v, 'Method ' . $methodName . '() must take array as argument' );
}
if ($returnTypeUndeclared) {
$fnDef->returnType = self::TYPE_OBJECT;
} elseif ($fnDef->returnType !== self::TYPE_OBJECT) {
$this->fatalError($v, 'Method ' . $this->class . "::{$name}() must return object" );
$this->fatalError($v, 'Method ' . $methodName . '() must return object' );
}
} elseif ($nameLower == '__debuginfo') {
if ($returnTypeUndeclared) {
$fnDef->returnType = self::TYPE_ARRAY;
} elseif ($fnDef->returnType !== self::TYPE_ARRAY) {
$this->fatalError($v, 'Method ' . $this->class . "::{$name}() must return array" );
$this->fatalError($v, 'Method ' . $methodName . '() must return array' );
}
} elseif ($nameLower == '__sleep') {
if ($returnTypeUndeclared) {
$fnDef->returnType = self::TYPE_ARRAY;
} elseif ($fnDef->returnType !== self::TYPE_ARRAY) {
$this->fatalError($v, 'Method ' . $this->class . "::{$name}() must return array" );
$this->fatalError($v, 'Method ' . $methodName . '() must return array' );
}
} elseif ($nameLower == '__wakeup') {
if ($returnTypeUndeclared) {
$fnDef->returnType = self::TYPE_VOID;
} elseif ($fnDef->returnType !== self::TYPE_VOID) {
$this->fatalError($v, 'Method ' . $this->class . "::{$name}() must return void" );
$this->fatalError($v, 'Method ' . $methodName . '() must return void' );
}
} elseif ($nameLower == '__clone') {
if ($returnTypeUndeclared) {
$fnDef->returnType = self::TYPE_VOID;
} elseif ($fnDef->returnType !== self::TYPE_VOID) {
$this->fatalError($v, 'Method ' . $this->class . "::{$name}() must return void" );
$this->fatalError($v, 'Method ' . $methodName . '() must return void' );
}
}