2016-01-07 18:28:27 -05:00
|
|
|
<?php
|
2016-08-13 14:20:46 -04:00
|
|
|
namespace Psalm\Checker;
|
2016-01-07 18:28:27 -05:00
|
|
|
|
2016-11-02 02:29:00 -04:00
|
|
|
use PhpParser;
|
2016-12-03 19:11:30 -05:00
|
|
|
use Psalm\CodeLocation;
|
2016-11-02 02:29:00 -04:00
|
|
|
use Psalm\Config;
|
|
|
|
use Psalm\Exception\DocblockParseException;
|
2016-07-25 18:37:44 -04:00
|
|
|
use Psalm\Issue\DeprecatedMethod;
|
2016-11-02 02:29:00 -04:00
|
|
|
use Psalm\Issue\InaccessibleMethod;
|
2016-07-25 18:37:44 -04:00
|
|
|
use Psalm\Issue\InvalidDocblock;
|
|
|
|
use Psalm\Issue\InvalidStaticInvocation;
|
2016-12-31 09:20:10 -05:00
|
|
|
use Psalm\Issue\NonStaticSelfCall;
|
2016-11-02 02:29:00 -04:00
|
|
|
use Psalm\Issue\UndefinedMethod;
|
|
|
|
use Psalm\IssueBuffer;
|
2016-08-13 14:20:46 -04:00
|
|
|
use Psalm\StatementsSource;
|
2016-12-30 15:53:35 -05:00
|
|
|
use Psalm\Storage\MethodStorage;
|
2016-08-13 14:20:46 -04:00
|
|
|
use Psalm\Type;
|
2016-01-07 18:28:27 -05:00
|
|
|
|
2016-08-15 15:14:28 -04:00
|
|
|
class MethodChecker extends FunctionLikeChecker
|
2016-01-07 18:28:27 -05:00
|
|
|
{
|
2016-11-02 02:29:00 -04:00
|
|
|
const VISIBILITY_PUBLIC = 1;
|
|
|
|
const VISIBILITY_PROTECTED = 2;
|
|
|
|
const VISIBILITY_PRIVATE = 3;
|
|
|
|
|
2016-10-30 12:46:18 -04:00
|
|
|
/**
|
|
|
|
* @param PhpParser\Node\FunctionLike $function
|
|
|
|
* @param StatementsSource $source
|
|
|
|
* @param array $this_vars
|
2016-12-17 00:48:31 -05:00
|
|
|
* @psalm-suppress MixedAssignment
|
2016-10-30 12:46:18 -04:00
|
|
|
*/
|
|
|
|
public function __construct($function, StatementsSource $source, array $this_vars = [])
|
2016-04-16 16:28:25 -04:00
|
|
|
{
|
2016-10-15 00:12:57 -04:00
|
|
|
if (!$function instanceof PhpParser\Node\Stmt\ClassMethod) {
|
|
|
|
throw new \InvalidArgumentException('Must be called with a ClassMethod');
|
|
|
|
}
|
|
|
|
|
2016-04-16 16:28:25 -04:00
|
|
|
parent::__construct($function, $source);
|
|
|
|
|
2016-10-15 00:12:57 -04:00
|
|
|
$this->registerMethod($function);
|
|
|
|
$this->is_static = $function->isStatic();
|
2016-04-16 16:28:25 -04:00
|
|
|
}
|
|
|
|
|
2016-10-15 00:12:57 -04:00
|
|
|
/**
|
|
|
|
* @param string $method_id
|
2016-12-24 11:03:55 +00:00
|
|
|
* @return array<int, \Psalm\FunctionLikeParameter>|null
|
2016-10-15 00:12:57 -04:00
|
|
|
*/
|
2016-04-16 16:28:25 -04:00
|
|
|
public static function getMethodParams($method_id)
|
|
|
|
{
|
2016-08-14 23:24:16 -04:00
|
|
|
self::registerClassMethod($method_id);
|
|
|
|
|
2016-11-06 23:29:54 -05:00
|
|
|
if ($method_id = self::getDeclaringMethodId($method_id)) {
|
2016-12-30 15:53:35 -05:00
|
|
|
$storage = self::getStorage($method_id);
|
|
|
|
|
|
|
|
if ($storage) {
|
|
|
|
return $storage->params;
|
|
|
|
}
|
2016-11-06 23:29:54 -05:00
|
|
|
}
|
2016-04-16 16:28:25 -04:00
|
|
|
}
|
|
|
|
|
2016-10-18 17:55:07 -04:00
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
public static function isVariadic($method_id)
|
|
|
|
{
|
|
|
|
self::registerClassMethod($method_id);
|
|
|
|
|
2016-12-30 15:53:35 -05:00
|
|
|
$method_id = (string)self::getDeclaringMethodId($method_id);
|
|
|
|
|
|
|
|
list($fq_class_name, $method_name) = explode('::', $method_id);
|
2016-10-18 17:55:07 -04:00
|
|
|
|
2016-12-30 15:53:35 -05:00
|
|
|
return ClassLikeChecker::$storage[$fq_class_name]->methods[$method_name]->variadic;
|
2016-10-18 17:55:07 -04:00
|
|
|
}
|
|
|
|
|
2016-10-09 17:54:58 -04:00
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
* @return Type\Union|null
|
|
|
|
*/
|
2016-12-06 16:33:47 -05:00
|
|
|
public static function getMethodReturnType($method_id)
|
2016-04-16 16:28:25 -04:00
|
|
|
{
|
2016-11-06 23:29:54 -05:00
|
|
|
/** @var string */
|
2016-10-15 00:12:57 -04:00
|
|
|
$method_id = self::getDeclaringMethodId($method_id);
|
2016-04-16 16:28:25 -04:00
|
|
|
|
2016-12-30 15:53:35 -05:00
|
|
|
list($fq_class_name, $method_name) = explode('::', $method_id);
|
2016-12-12 18:49:26 -05:00
|
|
|
|
2016-12-30 15:53:35 -05:00
|
|
|
if (!ClassLikeChecker::isUserDefined($fq_class_name) && FunctionChecker::inCallMap($method_id)) {
|
2016-12-12 18:49:26 -05:00
|
|
|
return FunctionChecker::getReturnTypeFromCallMap($method_id);
|
|
|
|
}
|
|
|
|
|
2016-12-30 15:53:35 -05:00
|
|
|
$storage = self::getStorage($method_id);
|
|
|
|
|
|
|
|
if (!$storage) {
|
|
|
|
throw new \UnexpectedValueException('$storage should not be null');
|
2016-10-15 00:12:57 -04:00
|
|
|
}
|
|
|
|
|
2016-12-30 15:53:35 -05:00
|
|
|
if ($storage->return_type) {
|
|
|
|
return clone $storage->return_type;
|
|
|
|
}
|
2016-10-15 00:12:57 -04:00
|
|
|
|
2016-12-30 15:53:35 -05:00
|
|
|
$class_storage = ClassLikeChecker::$storage[$fq_class_name];
|
2016-10-15 18:55:17 -04:00
|
|
|
|
2016-12-30 15:53:35 -05:00
|
|
|
foreach ($class_storage->overridden_method_ids[$method_name] as $overridden_method_id) {
|
|
|
|
$overridden_storage = self::getStorage($overridden_method_id);
|
|
|
|
|
|
|
|
if ($overridden_storage && $overridden_storage->return_type) {
|
|
|
|
if ($overridden_storage->return_type->isNull()) {
|
2016-10-15 18:55:17 -04:00
|
|
|
return Type::getVoid();
|
|
|
|
}
|
|
|
|
|
2016-12-30 15:53:35 -05:00
|
|
|
return clone $overridden_storage->return_type;
|
2016-10-15 00:12:57 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
2016-04-16 16:28:25 -04:00
|
|
|
}
|
|
|
|
|
2016-12-06 16:33:47 -05:00
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
* @param CodeLocation|null $defined_location
|
|
|
|
* @return CodeLocation|null
|
|
|
|
*/
|
|
|
|
public static function getMethodReturnTypeLocation($method_id, CodeLocation &$defined_location = null)
|
|
|
|
{
|
|
|
|
self::registerClassMethod($method_id);
|
|
|
|
|
|
|
|
/** @var string */
|
|
|
|
$method_id = self::getDeclaringMethodId($method_id);
|
|
|
|
|
2016-12-30 15:53:35 -05:00
|
|
|
$storage = self::getStorage($method_id);
|
|
|
|
|
|
|
|
if (!$storage) {
|
|
|
|
throw new \UnexpectedValueException('$storage should not be null');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$storage->return_type_location) {
|
2016-12-06 16:33:47 -05:00
|
|
|
$overridden_method_ids = self::getOverriddenMethodIds($method_id);
|
|
|
|
|
|
|
|
foreach ($overridden_method_ids as $overridden_method_id) {
|
2016-12-30 15:53:35 -05:00
|
|
|
$overridden_storage = self::getStorage($overridden_method_id);
|
|
|
|
|
|
|
|
if ($overridden_storage && $overridden_storage->return_type_location) {
|
|
|
|
$defined_location = $overridden_storage->return_type_location;
|
2016-12-06 16:33:47 -05:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-30 15:53:35 -05:00
|
|
|
return $storage->return_type_location;
|
2016-12-06 16:33:47 -05:00
|
|
|
}
|
|
|
|
|
2016-04-26 18:42:48 -04:00
|
|
|
/**
|
2016-11-02 02:29:00 -04:00
|
|
|
* @param \ReflectionMethod $method
|
|
|
|
* @return null
|
2016-04-26 18:42:48 -04:00
|
|
|
*/
|
2016-08-14 23:24:16 -04:00
|
|
|
public static function extractReflectionMethodInfo(\ReflectionMethod $method)
|
2016-04-16 16:28:25 -04:00
|
|
|
{
|
2016-12-30 15:53:35 -05:00
|
|
|
$method_name = strtolower($method->getName());
|
|
|
|
|
|
|
|
$class_storage = ClassLikeChecker::$storage[$method->class];
|
|
|
|
$storage = $class_storage->methods[strtolower($method_name)] = new MethodStorage();
|
|
|
|
|
|
|
|
$storage->cased_name = $method->name;
|
2016-12-07 18:07:40 -05:00
|
|
|
|
2016-11-21 16:44:35 -05:00
|
|
|
if (strtolower((string)$method->name) === strtolower((string)$method->class)) {
|
2016-12-30 15:53:35 -05:00
|
|
|
self::setDeclaringMethodId($method->class . '::__construct', $method->class . '::' . $method_name);
|
2016-12-31 11:49:04 -05:00
|
|
|
self::setAppearingMethodId($method->class . '::__construct', $method->class . '::' . $method_name);
|
2016-11-21 14:36:06 -05:00
|
|
|
}
|
2016-06-17 16:58:15 -04:00
|
|
|
|
2016-12-30 15:53:35 -05:00
|
|
|
if ($storage->reflected) {
|
2016-11-02 02:29:00 -04:00
|
|
|
return null;
|
2016-06-17 16:58:15 -04:00
|
|
|
}
|
|
|
|
|
2016-10-15 00:12:57 -04:00
|
|
|
/** @var \ReflectionClass */
|
|
|
|
$declaring_class = $method->getDeclaringClass();
|
|
|
|
|
2016-12-30 15:53:35 -05:00
|
|
|
$storage->reflected = true;
|
2016-04-16 16:28:25 -04:00
|
|
|
|
2016-12-30 15:53:35 -05:00
|
|
|
$storage->is_static = $method->isStatic();
|
|
|
|
$storage->file_name = $method->getFileName();
|
|
|
|
$storage->namespace = $declaring_class->getNamespaceName();
|
|
|
|
$class_storage->declaring_method_ids[$method_name] =
|
|
|
|
$declaring_class->name . '::' . strtolower((string)$method->getName());
|
2016-12-31 11:49:04 -05:00
|
|
|
|
|
|
|
$class_storage->appearing_method_ids[$method_name] = $class_storage->declaring_method_ids[$method_name];
|
2016-12-30 15:53:35 -05:00
|
|
|
$class_storage->overridden_method_ids[$method_name] = [];
|
|
|
|
|
|
|
|
$storage->visibility = $method->isPrivate()
|
2016-11-02 02:29:00 -04:00
|
|
|
? self::VISIBILITY_PRIVATE
|
|
|
|
: ($method->isProtected() ? self::VISIBILITY_PROTECTED : self::VISIBILITY_PUBLIC);
|
2016-08-05 15:11:20 -04:00
|
|
|
|
2016-04-16 16:28:25 -04:00
|
|
|
$params = $method->getParameters();
|
|
|
|
|
2016-06-24 18:18:11 -04:00
|
|
|
$method_param_names = [];
|
2016-07-10 16:09:09 -04:00
|
|
|
$method_param_types = [];
|
2016-06-24 18:18:11 -04:00
|
|
|
|
2016-12-30 15:53:35 -05:00
|
|
|
$storage->params = [];
|
2016-07-29 14:48:04 -04:00
|
|
|
|
2016-10-15 00:12:57 -04:00
|
|
|
/** @var \ReflectionParameter $param */
|
2016-08-13 23:26:45 -04:00
|
|
|
foreach ($params as $param) {
|
|
|
|
$param_array = self::getReflectionParamArray($param);
|
2016-12-30 15:53:35 -05:00
|
|
|
$storage->params[] = $param_array;
|
2016-08-13 23:26:45 -04:00
|
|
|
$method_param_names[$param->name] = true;
|
2016-10-09 17:54:58 -04:00
|
|
|
$method_param_types[$param->name] = $param_array->type;
|
2016-04-16 16:28:25 -04:00
|
|
|
}
|
|
|
|
|
2016-06-14 19:22:29 -04:00
|
|
|
$return_types = null;
|
2016-04-16 16:28:25 -04:00
|
|
|
|
2016-06-24 18:18:11 -04:00
|
|
|
$config = Config::getInstance();
|
2016-04-16 16:28:25 -04:00
|
|
|
|
2016-06-24 18:18:11 -04:00
|
|
|
$return_type = null;
|
|
|
|
|
2016-12-30 15:53:35 -05:00
|
|
|
$storage->return_type = $return_type;
|
2016-11-02 02:29:00 -04:00
|
|
|
return null;
|
2016-04-16 16:28:25 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determines whether a given method is static or not
|
2016-11-02 02:29:00 -04:00
|
|
|
*
|
2016-12-24 18:23:22 +00:00
|
|
|
* @param string $method_id
|
2016-12-31 09:20:10 -05:00
|
|
|
* @param bool $self_call
|
2016-12-24 18:23:22 +00:00
|
|
|
* @param CodeLocation $code_location
|
|
|
|
* @param array<string> $suppressed_issues
|
2016-11-02 02:29:00 -04:00
|
|
|
* @return bool
|
2016-04-16 16:28:25 -04:00
|
|
|
*/
|
2016-12-31 09:20:10 -05:00
|
|
|
public static function checkMethodStatic(
|
|
|
|
$method_id,
|
|
|
|
$self_call,
|
|
|
|
CodeLocation $code_location,
|
|
|
|
array $suppressed_issues
|
|
|
|
) {
|
2016-08-14 23:24:16 -04:00
|
|
|
self::registerClassMethod($method_id);
|
|
|
|
|
2016-11-06 23:29:54 -05:00
|
|
|
/** @var string */
|
2016-10-15 00:12:57 -04:00
|
|
|
$method_id = self::getDeclaringMethodId($method_id);
|
2016-04-16 16:28:25 -04:00
|
|
|
|
2016-12-30 15:53:35 -05:00
|
|
|
$storage = self::getStorage($method_id);
|
|
|
|
|
|
|
|
if (!$storage) {
|
|
|
|
throw new \UnexpectedValueException('$storage should not be null');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$storage->is_static) {
|
2016-12-31 09:20:10 -05:00
|
|
|
if ($self_call) {
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new NonStaticSelfCall(
|
|
|
|
'Method ' . MethodChecker::getCasedMethodId($method_id) . ' is not static, but is called using self::',
|
|
|
|
$code_location
|
|
|
|
),
|
|
|
|
$suppressed_issues
|
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new InvalidStaticInvocation(
|
|
|
|
'Method ' . MethodChecker::getCasedMethodId($method_id) . ' is not static, but is called statically',
|
|
|
|
$code_location
|
|
|
|
),
|
|
|
|
$suppressed_issues
|
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-07-22 13:29:46 -04:00
|
|
|
}
|
|
|
|
}
|
2016-11-02 02:29:00 -04:00
|
|
|
|
|
|
|
return true;
|
2016-04-16 16:28:25 -04:00
|
|
|
}
|
|
|
|
|
2016-11-02 02:29:00 -04:00
|
|
|
/**
|
|
|
|
* @param PhpParser\Node\Stmt\ClassMethod $method
|
2016-11-04 20:48:01 -04:00
|
|
|
* @return null|false
|
2016-12-17 00:48:31 -05:00
|
|
|
* @psalm-suppress MixedAssignment
|
2016-11-02 02:29:00 -04:00
|
|
|
*/
|
2016-08-13 18:54:49 -04:00
|
|
|
protected function registerMethod(PhpParser\Node\Stmt\ClassMethod $method)
|
2016-04-16 16:28:25 -04:00
|
|
|
{
|
2016-12-07 18:07:40 -05:00
|
|
|
$method_id = $this->fq_class_name . '::' . strtolower($method->name);
|
2016-12-30 15:53:35 -05:00
|
|
|
|
|
|
|
$class_storage = ClassLikeChecker::$storage[$this->fq_class_name];
|
|
|
|
$storage = $class_storage->methods[strtolower($method->name)] = new MethodStorage();
|
|
|
|
|
|
|
|
$cased_method_id = $storage->cased_name = $method->name;
|
2016-05-22 12:14:48 -04:00
|
|
|
|
2016-12-27 01:06:05 +00:00
|
|
|
if (strtolower((string)$method->name) === strtolower((string)$this->class_name)) {
|
2016-12-07 18:07:40 -05:00
|
|
|
self::setDeclaringMethodId($this->fq_class_name . '::__construct', $method_id);
|
2016-12-31 11:49:04 -05:00
|
|
|
self::setAppearingMethodId($this->fq_class_name . '::__construct', $method_id);
|
2016-12-07 18:07:40 -05:00
|
|
|
}
|
|
|
|
|
2016-12-30 15:53:35 -05:00
|
|
|
if ($storage->reflected || $storage->registered) {
|
|
|
|
$this->suppressed_issues = $storage->suppressed_issues;
|
2016-12-28 21:40:03 -05:00
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$this->source instanceof TraitChecker) {
|
2016-12-30 15:53:35 -05:00
|
|
|
$storage->registered = true;
|
2016-12-28 21:40:03 -05:00
|
|
|
}
|
2016-04-16 16:28:25 -04:00
|
|
|
|
2016-12-30 15:53:35 -05:00
|
|
|
$class_storage->declaring_method_ids[strtolower($method->name)] = $method_id;
|
2016-12-31 11:49:04 -05:00
|
|
|
$class_storage->appearing_method_ids[strtolower($method->name)] = $method_id;
|
2016-04-16 16:28:25 -04:00
|
|
|
|
2016-12-30 15:53:35 -05:00
|
|
|
if (!isset($class_storage->overridden_method_ids[strtolower($method->name)])) {
|
|
|
|
$class_storage->overridden_method_ids[strtolower($method->name)] = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
$storage->is_static = $method->isStatic();
|
|
|
|
|
|
|
|
$storage->namespace = $this->namespace;
|
|
|
|
$storage->file_name = $this->file_name;
|
2016-04-16 16:28:25 -04:00
|
|
|
|
2016-06-24 18:18:11 -04:00
|
|
|
if ($method->isPrivate()) {
|
2016-12-30 15:53:35 -05:00
|
|
|
$storage->visibility = self::VISIBILITY_PRIVATE;
|
2016-11-02 02:29:00 -04:00
|
|
|
} elseif ($method->isProtected()) {
|
2016-12-30 15:53:35 -05:00
|
|
|
$storage->visibility = self::VISIBILITY_PROTECTED;
|
2016-11-02 02:29:00 -04:00
|
|
|
} else {
|
2016-12-30 15:53:35 -05:00
|
|
|
$storage->visibility = self::VISIBILITY_PUBLIC;
|
2016-04-16 16:28:25 -04:00
|
|
|
}
|
|
|
|
|
2016-06-24 18:18:11 -04:00
|
|
|
$method_param_names = [];
|
|
|
|
|
2016-04-16 16:28:25 -04:00
|
|
|
foreach ($method->getParams() as $param) {
|
2016-11-02 02:29:00 -04:00
|
|
|
$param_array = $this->getTranslatedParam(
|
|
|
|
$param,
|
2016-12-03 19:11:30 -05:00
|
|
|
$this
|
2016-11-02 02:29:00 -04:00
|
|
|
);
|
|
|
|
|
2016-12-30 15:53:35 -05:00
|
|
|
$storage->params[] = $param_array;
|
2016-10-09 17:54:58 -04:00
|
|
|
$method_param_names[$param->name] = $param_array->type;
|
2016-04-16 16:28:25 -04:00
|
|
|
}
|
2016-06-24 18:18:11 -04:00
|
|
|
|
|
|
|
$config = Config::getInstance();
|
|
|
|
$return_type = null;
|
2016-12-06 16:33:47 -05:00
|
|
|
$return_type_location = null;
|
2016-06-24 18:18:11 -04:00
|
|
|
|
2016-10-15 00:12:57 -04:00
|
|
|
$doc_comment = $method->getDocComment();
|
2016-06-24 18:18:11 -04:00
|
|
|
|
2016-12-30 15:53:35 -05:00
|
|
|
$storage->suppressed_issues = [];
|
2016-07-22 13:29:46 -04:00
|
|
|
|
2016-10-18 21:54:08 -04:00
|
|
|
if (isset($method->returnType)) {
|
2016-12-04 00:08:25 -05:00
|
|
|
$parser_return_type = $method->returnType;
|
|
|
|
|
|
|
|
$suffix = '';
|
|
|
|
|
|
|
|
if ($parser_return_type instanceof PhpParser\Node\NullableType) {
|
|
|
|
$suffix = '|null';
|
|
|
|
$parser_return_type = $parser_return_type->type;
|
|
|
|
}
|
|
|
|
|
2016-12-31 11:49:04 -05:00
|
|
|
$return_type_string = (is_string($parser_return_type)
|
|
|
|
? $parser_return_type
|
|
|
|
: ClassLikeChecker::getFQCLNFromNameObject(
|
|
|
|
$parser_return_type,
|
|
|
|
$this->namespace,
|
|
|
|
$this->getAliasedClasses()
|
|
|
|
)
|
|
|
|
) . $suffix;
|
|
|
|
|
|
|
|
$return_type = Type::parseString($return_type_string);
|
2016-12-06 16:33:47 -05:00
|
|
|
|
|
|
|
$return_type_location = new CodeLocation($this->getSource(), $method, false, self::RETURN_TYPE_REGEX);
|
2016-10-18 21:54:08 -04:00
|
|
|
}
|
|
|
|
|
2016-10-15 00:12:57 -04:00
|
|
|
if ($doc_comment) {
|
2016-10-28 10:54:20 -04:00
|
|
|
$docblock_info = null;
|
2016-10-15 00:12:57 -04:00
|
|
|
|
2016-10-28 10:54:20 -04:00
|
|
|
try {
|
2016-12-03 19:11:30 -05:00
|
|
|
$docblock_info = CommentChecker::extractDocblockInfo((string)$doc_comment, $doc_comment->getLine());
|
2016-11-02 02:29:00 -04:00
|
|
|
} catch (DocblockParseException $e) {
|
2016-10-28 10:54:20 -04:00
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new InvalidDocblock(
|
2016-11-07 17:07:59 -05:00
|
|
|
'Invalid type passed in docblock for ' . $cased_method_id,
|
2016-12-03 19:11:30 -05:00
|
|
|
new CodeLocation($this, $method)
|
2016-10-28 10:54:20 -04:00
|
|
|
)
|
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-10-18 17:55:07 -04:00
|
|
|
}
|
|
|
|
|
2016-10-28 10:54:20 -04:00
|
|
|
if ($docblock_info) {
|
2016-11-12 23:59:31 -05:00
|
|
|
if ($docblock_info->deprecated) {
|
2016-12-30 15:53:35 -05:00
|
|
|
$storage->deprecated = true;
|
2016-10-15 00:12:57 -04:00
|
|
|
}
|
|
|
|
|
2016-11-12 23:59:31 -05:00
|
|
|
if ($docblock_info->variadic) {
|
2016-12-30 15:53:35 -05:00
|
|
|
$storage->variadic = true;
|
2016-10-28 10:54:20 -04:00
|
|
|
}
|
|
|
|
|
2016-11-12 23:59:31 -05:00
|
|
|
$this->suppressed_issues = $docblock_info->suppress;
|
2016-12-30 15:53:35 -05:00
|
|
|
$storage->suppressed_issues = $this->suppressed_issues;
|
2016-10-28 10:54:20 -04:00
|
|
|
|
|
|
|
if ($config->use_docblock_types) {
|
2016-11-12 23:59:31 -05:00
|
|
|
if ($docblock_info->return_type) {
|
2016-12-11 23:40:46 -05:00
|
|
|
$docblock_return_type = Type::parseString(
|
2016-11-02 02:29:00 -04:00
|
|
|
$this->fixUpLocalType(
|
2016-11-12 23:59:31 -05:00
|
|
|
(string)$docblock_info->return_type,
|
2016-11-07 17:29:51 -05:00
|
|
|
$this->fq_class_name,
|
2016-11-02 02:29:00 -04:00
|
|
|
$this->namespace,
|
|
|
|
$this->getAliasedClasses()
|
|
|
|
)
|
|
|
|
);
|
2016-12-06 16:33:47 -05:00
|
|
|
|
|
|
|
if (!$return_type_location) {
|
|
|
|
$return_type_location = new CodeLocation($this->getSource(), $method, true);
|
|
|
|
}
|
|
|
|
|
2016-12-11 23:40:46 -05:00
|
|
|
if ($return_type && !TypeChecker::hasIdenticalTypes($return_type, $docblock_return_type)) {
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new InvalidDocblock(
|
|
|
|
'Docblock return type does not match method return type for ' . $this->getMethodId(),
|
|
|
|
new CodeLocation($this, $method, true)
|
|
|
|
)
|
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$return_type = $docblock_return_type;
|
|
|
|
}
|
|
|
|
|
2016-12-06 16:33:47 -05:00
|
|
|
$return_type_location->setCommentLine($docblock_info->return_type_line_number);
|
2016-10-28 10:54:20 -04:00
|
|
|
}
|
|
|
|
|
2016-11-12 23:59:31 -05:00
|
|
|
if ($docblock_info->params) {
|
2016-10-28 10:54:20 -04:00
|
|
|
$this->improveParamsFromDocblock(
|
2016-11-12 23:59:31 -05:00
|
|
|
$docblock_info->params,
|
2016-10-28 10:54:20 -04:00
|
|
|
$method_param_names,
|
2016-12-30 15:53:35 -05:00
|
|
|
$storage->params,
|
2016-12-03 19:11:30 -05:00
|
|
|
new CodeLocation($this, $method, true)
|
2016-10-28 10:54:20 -04:00
|
|
|
);
|
|
|
|
}
|
2016-10-15 00:12:57 -04:00
|
|
|
}
|
2016-06-24 18:18:11 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-30 15:53:35 -05:00
|
|
|
$storage->return_type_location = $return_type_location;
|
|
|
|
$storage->return_type = $return_type;
|
2016-11-02 02:29:00 -04:00
|
|
|
return null;
|
2016-04-16 16:28:25 -04:00
|
|
|
}
|
|
|
|
|
2016-10-15 00:12:57 -04:00
|
|
|
/**
|
|
|
|
* @param string $return_type
|
|
|
|
* @param string $method_id
|
|
|
|
* @return string
|
|
|
|
*/
|
2016-08-13 18:54:49 -04:00
|
|
|
protected static function fixUpReturnType($return_type, $method_id)
|
2016-04-16 16:28:25 -04:00
|
|
|
{
|
2016-12-30 15:53:35 -05:00
|
|
|
$storage = self::getStorage($method_id);
|
|
|
|
|
|
|
|
if (!$storage) {
|
|
|
|
throw new \UnexpectedValueException('$storage should not be null');
|
|
|
|
}
|
|
|
|
|
2016-04-16 16:28:25 -04:00
|
|
|
if (strpos($return_type, '[') !== false) {
|
2016-07-12 00:50:16 -04:00
|
|
|
$return_type = Type::convertSquareBrackets($return_type);
|
2016-04-16 16:28:25 -04:00
|
|
|
}
|
|
|
|
|
2016-07-12 00:50:16 -04:00
|
|
|
$return_type_tokens = Type::tokenize($return_type);
|
2016-04-16 16:28:25 -04:00
|
|
|
|
2016-10-03 16:39:42 -04:00
|
|
|
foreach ($return_type_tokens as $i => &$return_type_token) {
|
2016-04-16 16:28:25 -04:00
|
|
|
if ($return_type_token[0] === '\\') {
|
|
|
|
$return_type_token = substr($return_type_token, 1);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-10-03 16:39:42 -04:00
|
|
|
if (in_array($return_type_token, ['<', '>', '|', '?', ',', '{', '}', ':'])) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($return_type_token[$i + 1]) && $return_type_token[$i + 1] === ':') {
|
2016-04-16 16:28:25 -04:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-07-12 00:50:16 -04:00
|
|
|
$return_type_token = Type::fixScalarTerms($return_type_token);
|
|
|
|
|
2016-04-16 16:28:25 -04:00
|
|
|
if ($return_type_token[0] === strtoupper($return_type_token[0])) {
|
2016-11-07 17:29:51 -05:00
|
|
|
$fq_class_name = explode('::', $method_id)[0];
|
2016-04-16 16:28:25 -04:00
|
|
|
|
|
|
|
if ($return_type_token === '$this') {
|
2016-11-07 17:29:51 -05:00
|
|
|
$return_type_token = $fq_class_name;
|
2016-04-16 16:28:25 -04:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-11-07 19:16:51 -05:00
|
|
|
$return_type_token = FileChecker::getFQCLNFromNameInFile(
|
2016-10-15 00:12:57 -04:00
|
|
|
$return_type_token,
|
2016-12-30 15:53:35 -05:00
|
|
|
$storage->namespace,
|
|
|
|
$storage->file_name
|
2016-10-15 00:12:57 -04:00
|
|
|
);
|
2016-04-16 16:28:25 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return implode('', $return_type_tokens);
|
|
|
|
}
|
|
|
|
|
2016-04-26 18:42:48 -04:00
|
|
|
/**
|
2016-12-03 19:11:30 -05:00
|
|
|
* @param string $method_id
|
|
|
|
* @param CodeLocation $code_location
|
|
|
|
* @param array $suppressed_issues
|
2016-07-24 16:21:42 -04:00
|
|
|
* @return bool|null
|
2016-04-26 18:42:48 -04:00
|
|
|
*/
|
2016-12-03 19:11:30 -05:00
|
|
|
public static function checkMethodExists($method_id, CodeLocation $code_location, array $suppressed_issues)
|
2016-10-23 12:24:53 -04:00
|
|
|
{
|
|
|
|
if (self::methodExists($method_id)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (IssueBuffer::accepts(
|
2016-12-03 19:11:30 -05:00
|
|
|
new UndefinedMethod('Method ' . $method_id . ' does not exist', $code_location),
|
2016-11-02 02:29:00 -04:00
|
|
|
$suppressed_issues
|
2016-10-23 12:24:53 -04:00
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-11-02 02:29:00 -04:00
|
|
|
|
|
|
|
return null;
|
2016-10-23 12:24:53 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Whether or not a given method exists
|
2016-11-02 02:29:00 -04:00
|
|
|
*
|
2016-10-23 12:24:53 -04:00
|
|
|
* @param string $method_id
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public static function methodExists($method_id)
|
2016-04-16 16:28:25 -04:00
|
|
|
{
|
2016-08-15 13:59:02 -04:00
|
|
|
// remove trailing backslash if it exists
|
|
|
|
$method_id = preg_replace('/^\\\\/', '', $method_id);
|
2016-08-15 13:37:21 -04:00
|
|
|
$method_parts = explode('::', $method_id);
|
2016-10-18 17:55:07 -04:00
|
|
|
$method_parts[1] = strtolower($method_parts[1]);
|
|
|
|
$method_id = implode('::', $method_parts);
|
2016-08-15 13:37:21 -04:00
|
|
|
|
2016-11-21 14:36:06 -05:00
|
|
|
$old_method_id = null;
|
|
|
|
|
2016-12-30 20:39:12 -05:00
|
|
|
if (ClassLikeChecker::registerClass($method_parts[0]) === false) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-12-30 15:53:35 -05:00
|
|
|
|
|
|
|
$class_storage = ClassLikeChecker::$storage[$method_parts[0]];
|
|
|
|
|
|
|
|
if (isset($class_storage->declaring_method_ids[$method_parts[1]])) {
|
2016-07-24 16:21:42 -04:00
|
|
|
return true;
|
2016-07-23 10:58:53 -04:00
|
|
|
}
|
|
|
|
|
2016-11-21 14:36:06 -05:00
|
|
|
// support checking oldstyle constructors
|
2016-10-18 17:55:07 -04:00
|
|
|
if ($method_parts[1] === '__construct') {
|
2016-11-21 15:51:38 -05:00
|
|
|
$method_part_parts = explode('\\', $method_parts[0]);
|
|
|
|
$old_constructor_name = array_pop($method_part_parts);
|
2016-11-21 14:36:06 -05:00
|
|
|
$old_method_id = $method_parts[0] . '::' . $old_constructor_name;
|
2016-10-18 17:55:07 -04:00
|
|
|
}
|
|
|
|
|
2016-11-21 14:36:06 -05:00
|
|
|
if (FunctionChecker::inCallMap($method_id) || ($old_method_id && FunctionChecker::inCallMap($method_id))) {
|
2016-10-22 17:35:59 -04:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-10-23 12:24:53 -04:00
|
|
|
return false;
|
2016-04-18 13:31:59 -04:00
|
|
|
}
|
|
|
|
|
2016-10-15 00:12:57 -04:00
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
* @return void
|
|
|
|
*/
|
2016-08-15 02:12:27 -04:00
|
|
|
public static function registerClassMethod($method_id)
|
2016-04-18 13:31:59 -04:00
|
|
|
{
|
2016-08-14 23:24:16 -04:00
|
|
|
ClassLikeChecker::registerClass(explode('::', $method_id)[0]);
|
2016-04-18 13:31:59 -04:00
|
|
|
}
|
|
|
|
|
2016-12-30 15:53:35 -05:00
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
* @return MethodStorage|null
|
|
|
|
*/
|
|
|
|
public static function getStorage($method_id)
|
|
|
|
{
|
|
|
|
list($fq_class_name, $method_name) = explode('::', $method_id);
|
|
|
|
|
|
|
|
ClassLikeChecker::registerClass($fq_class_name);
|
|
|
|
|
|
|
|
$class_storage = ClassLikeChecker::$storage[$fq_class_name];
|
|
|
|
|
|
|
|
if (isset($class_storage->methods[strtolower($method_name)])) {
|
|
|
|
return $class_storage->methods[strtolower($method_name)];
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2016-10-15 00:12:57 -04:00
|
|
|
/**
|
2016-12-03 19:11:30 -05:00
|
|
|
* @param string $method_id
|
|
|
|
* @param CodeLocation $code_location
|
|
|
|
* @param array $suppressed_issues
|
2016-10-15 00:12:57 -04:00
|
|
|
* @return false|null
|
|
|
|
*/
|
2016-12-03 19:11:30 -05:00
|
|
|
public static function checkMethodNotDeprecated($method_id, CodeLocation $code_location, array $suppressed_issues)
|
2016-07-22 13:29:46 -04:00
|
|
|
{
|
2016-12-30 15:53:35 -05:00
|
|
|
$method_id = (string) self::getDeclaringMethodId($method_id);
|
|
|
|
$storage = self::getStorage($method_id);
|
|
|
|
|
|
|
|
if (!$storage) {
|
|
|
|
throw new \UnexpectedValueException('$storage should not be null');
|
|
|
|
}
|
2016-07-22 13:29:46 -04:00
|
|
|
|
2016-12-30 15:53:35 -05:00
|
|
|
if ($storage->deprecated) {
|
2016-07-22 13:29:46 -04:00
|
|
|
if (IssueBuffer::accepts(
|
2016-11-02 02:29:00 -04:00
|
|
|
new DeprecatedMethod(
|
|
|
|
'The method ' . MethodChecker::getCasedMethodId($method_id) . ' has been marked as deprecated',
|
2016-12-03 19:11:30 -05:00
|
|
|
$code_location
|
2016-11-02 02:29:00 -04:00
|
|
|
),
|
|
|
|
$suppressed_issues
|
2016-07-22 13:29:46 -04:00
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2016-11-02 02:29:00 -04:00
|
|
|
|
|
|
|
return null;
|
2016-07-22 13:29:46 -04:00
|
|
|
}
|
|
|
|
|
2016-04-26 18:42:48 -04:00
|
|
|
/**
|
2016-08-13 11:10:43 -04:00
|
|
|
* @param string $method_id
|
2016-11-01 00:39:41 -04:00
|
|
|
* @param string|null $calling_context
|
2016-08-13 11:28:06 -04:00
|
|
|
* @param StatementsSource $source
|
2016-12-03 19:11:30 -05:00
|
|
|
* @param CodeLocation $code_location
|
2016-11-02 02:29:00 -04:00
|
|
|
* @param array $suppressed_issues
|
2016-06-13 19:57:32 -04:00
|
|
|
* @return false|null
|
2016-04-26 18:42:48 -04:00
|
|
|
*/
|
2016-11-02 02:29:00 -04:00
|
|
|
public static function checkMethodVisibility(
|
|
|
|
$method_id,
|
|
|
|
$calling_context,
|
|
|
|
StatementsSource $source,
|
2016-12-03 19:11:30 -05:00
|
|
|
CodeLocation $code_location,
|
2016-11-02 02:29:00 -04:00
|
|
|
array $suppressed_issues
|
|
|
|
) {
|
2016-08-14 23:24:16 -04:00
|
|
|
self::registerClassMethod($method_id);
|
|
|
|
|
2016-10-15 00:12:57 -04:00
|
|
|
$declared_method_id = self::getDeclaringMethodId($method_id);
|
2016-04-18 13:31:59 -04:00
|
|
|
|
2016-12-12 14:29:58 -05:00
|
|
|
$method_class = explode('::', (string)$method_id)[0];
|
|
|
|
$declaring_method_class = explode('::', (string)$declared_method_id)[0];
|
2016-04-30 14:14:22 -04:00
|
|
|
$method_name = explode('::', $method_id)[1];
|
2016-04-18 13:31:59 -04:00
|
|
|
|
2016-12-12 14:29:58 -05:00
|
|
|
if (TraitChecker::traitExists($declaring_method_class) && ClassLikeChecker::classUsesTrait($method_class, $declaring_method_class)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if the calling class is the same, we know the method exists, so it must be visible
|
|
|
|
if ($method_class === $calling_context) {
|
2016-12-12 13:50:46 -05:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2016-12-12 14:29:58 -05:00
|
|
|
if ($source->getSource() instanceof TraitChecker && $declaring_method_class === $source->getFQCLN()) {
|
2016-11-02 02:29:00 -04:00
|
|
|
return null;
|
2016-08-13 11:10:43 -04:00
|
|
|
}
|
|
|
|
|
2016-12-30 15:53:35 -05:00
|
|
|
$storage = self::getStorage((string)$declared_method_id);
|
|
|
|
|
|
|
|
if (!$storage) {
|
|
|
|
throw new \UnexpectedValueException('$storage should not be null');
|
|
|
|
}
|
|
|
|
|
|
|
|
switch ($storage->visibility) {
|
2016-04-18 13:31:59 -04:00
|
|
|
case self::VISIBILITY_PUBLIC:
|
2016-11-02 02:29:00 -04:00
|
|
|
return null;
|
2016-04-18 13:31:59 -04:00
|
|
|
|
|
|
|
case self::VISIBILITY_PRIVATE:
|
2016-12-12 14:29:58 -05:00
|
|
|
if (!$calling_context || $declaring_method_class !== $calling_context) {
|
2016-06-26 15:18:40 -04:00
|
|
|
if (IssueBuffer::accepts(
|
2016-07-22 13:29:46 -04:00
|
|
|
new InaccessibleMethod(
|
2016-11-02 02:29:00 -04:00
|
|
|
'Cannot access private method ' . MethodChecker::getCasedMethodId($method_id) .
|
|
|
|
' from context ' . $calling_context,
|
2016-12-03 19:11:30 -05:00
|
|
|
$code_location
|
2016-07-22 13:29:46 -04:00
|
|
|
),
|
2016-11-02 02:29:00 -04:00
|
|
|
$suppressed_issues
|
2016-06-05 20:25:16 -04:00
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-04-18 13:31:59 -04:00
|
|
|
}
|
2016-11-02 02:29:00 -04:00
|
|
|
|
|
|
|
return null;
|
2016-04-18 13:31:59 -04:00
|
|
|
|
|
|
|
case self::VISIBILITY_PROTECTED:
|
2016-12-12 14:29:58 -05:00
|
|
|
if ($declaring_method_class === $calling_context) {
|
2016-11-02 02:29:00 -04:00
|
|
|
return null;
|
2016-04-18 13:31:59 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!$calling_context) {
|
2016-06-26 15:18:40 -04:00
|
|
|
if (IssueBuffer::accepts(
|
2016-11-02 02:29:00 -04:00
|
|
|
new InaccessibleMethod(
|
|
|
|
'Cannot access protected method ' . $method_id,
|
2016-12-03 19:11:30 -05:00
|
|
|
$code_location
|
2016-11-02 02:29:00 -04:00
|
|
|
),
|
|
|
|
$suppressed_issues
|
2016-06-05 20:25:16 -04:00
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-11-01 00:39:41 -04:00
|
|
|
|
2016-11-02 02:29:00 -04:00
|
|
|
return null;
|
2016-04-18 13:31:59 -04:00
|
|
|
}
|
|
|
|
|
2016-12-12 14:29:58 -05:00
|
|
|
if (ClassChecker::classExtends($declaring_method_class, $calling_context) &&
|
2016-11-02 02:29:00 -04:00
|
|
|
MethodChecker::methodExists($calling_context . '::' . $method_name)
|
|
|
|
) {
|
|
|
|
return null;
|
2016-04-30 14:14:22 -04:00
|
|
|
}
|
|
|
|
|
2016-12-12 14:29:58 -05:00
|
|
|
if (!ClassChecker::classExtends($calling_context, $declaring_method_class)) {
|
2016-06-26 15:18:40 -04:00
|
|
|
if (IssueBuffer::accepts(
|
2016-07-22 13:29:46 -04:00
|
|
|
new InaccessibleMethod(
|
2016-11-02 02:29:00 -04:00
|
|
|
'Cannot access protected method ' . MethodChecker::getCasedMethodId($method_id) .
|
|
|
|
' from context ' . $calling_context,
|
2016-12-03 19:11:30 -05:00
|
|
|
$code_location
|
2016-07-22 13:29:46 -04:00
|
|
|
),
|
2016-11-02 02:29:00 -04:00
|
|
|
$suppressed_issues
|
2016-06-05 20:25:16 -04:00
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-04-18 13:31:59 -04:00
|
|
|
}
|
2016-04-16 16:28:25 -04:00
|
|
|
}
|
2016-11-02 02:29:00 -04:00
|
|
|
|
|
|
|
return null;
|
2016-04-16 16:28:25 -04:00
|
|
|
}
|
2016-04-17 12:27:47 -04:00
|
|
|
|
2016-10-15 00:12:57 -04:00
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
* @param string $declaring_method_id
|
2016-11-02 02:29:00 -04:00
|
|
|
* @return void
|
2016-10-15 00:12:57 -04:00
|
|
|
*/
|
|
|
|
public static function setDeclaringMethodId($method_id, $declaring_method_id)
|
2016-04-17 12:27:47 -04:00
|
|
|
{
|
2016-12-30 15:53:35 -05:00
|
|
|
list($fq_class_name, $method_name) = explode('::', $method_id);
|
2016-12-30 19:36:35 -05:00
|
|
|
|
2016-12-30 15:53:35 -05:00
|
|
|
ClassLikeChecker::$storage[$fq_class_name]->declaring_method_ids[$method_name] = $declaring_method_id;
|
2016-04-17 12:27:47 -04:00
|
|
|
}
|
2016-04-26 18:18:49 -04:00
|
|
|
|
2016-12-31 11:49:04 -05:00
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
* @param string $appearing_method_id
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public static function setAppearingMethodId($method_id, $appearing_method_id)
|
|
|
|
{
|
|
|
|
list($fq_class_name, $method_name) = explode('::', $method_id);
|
|
|
|
|
|
|
|
ClassLikeChecker::$storage[$fq_class_name]->appearing_method_ids[$method_name] = $appearing_method_id;
|
|
|
|
}
|
|
|
|
|
2016-10-12 01:38:29 -04:00
|
|
|
/**
|
|
|
|
* @param string $method_id
|
2016-11-06 23:29:54 -05:00
|
|
|
* @return string|null
|
2016-10-12 01:38:29 -04:00
|
|
|
*/
|
2016-10-15 00:12:57 -04:00
|
|
|
public static function getDeclaringMethodId($method_id)
|
2016-05-15 23:06:03 -04:00
|
|
|
{
|
2016-12-30 15:53:35 -05:00
|
|
|
list($fq_class_name, $method_name) = explode('::', $method_id);
|
2016-11-06 23:29:54 -05:00
|
|
|
|
2016-12-30 15:53:35 -05:00
|
|
|
if (isset(ClassLikeChecker::$storage[$fq_class_name]->declaring_method_ids[$method_name])) {
|
|
|
|
return ClassLikeChecker::$storage[$fq_class_name]->declaring_method_ids[$method_name];
|
|
|
|
}
|
2016-05-15 23:06:03 -04:00
|
|
|
}
|
|
|
|
|
2016-12-31 11:49:04 -05:00
|
|
|
/**
|
|
|
|
* Get the class this method appears in (vs is declared in, which could give a trait)
|
|
|
|
*
|
|
|
|
* @param string $method_id
|
|
|
|
* @return string|null
|
|
|
|
*/
|
|
|
|
public static function getAppearingMethodId($method_id)
|
|
|
|
{
|
|
|
|
list($fq_class_name, $method_name) = explode('::', $method_id);
|
|
|
|
|
|
|
|
if (isset(ClassLikeChecker::$storage[$fq_class_name]->appearing_method_ids[$method_name])) {
|
|
|
|
return ClassLikeChecker::$storage[$fq_class_name]->appearing_method_ids[$method_name];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-15 00:12:57 -04:00
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
* @param string $overridden_method_id
|
2016-11-02 02:29:00 -04:00
|
|
|
* @return void
|
2016-10-15 00:12:57 -04:00
|
|
|
*/
|
|
|
|
public static function setOverriddenMethodId($method_id, $overridden_method_id)
|
|
|
|
{
|
2016-12-30 15:53:35 -05:00
|
|
|
list($fq_class_name, $method_name) = explode('::', $method_id);
|
|
|
|
|
|
|
|
ClassLikeChecker::$storage[$fq_class_name]->overridden_method_ids[$method_name][] = $overridden_method_id;
|
2016-10-15 00:12:57 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
* @return array<string>
|
|
|
|
*/
|
|
|
|
public static function getOverriddenMethodIds($method_id)
|
|
|
|
{
|
2016-12-30 15:53:35 -05:00
|
|
|
list($fq_class_name, $method_name) = explode('::', $method_id);
|
|
|
|
|
2016-12-30 19:36:35 -05:00
|
|
|
$class_storage = ClassLikeChecker::$storage[$fq_class_name];
|
|
|
|
|
|
|
|
if (isset($class_storage->overridden_method_ids[$method_name])) {
|
|
|
|
return $class_storage->overridden_method_ids[$method_name];
|
|
|
|
}
|
|
|
|
|
|
|
|
return [];
|
2016-10-15 00:12:57 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-12-27 01:06:05 +00:00
|
|
|
* @param string $original_method_id
|
2016-10-15 00:12:57 -04:00
|
|
|
* @return string
|
|
|
|
*/
|
2016-12-27 01:06:05 +00:00
|
|
|
public static function getCasedMethodId($original_method_id)
|
2016-08-15 13:37:21 -04:00
|
|
|
{
|
2016-12-27 01:06:05 +00:00
|
|
|
$method_id = self::getDeclaringMethodId($original_method_id);
|
|
|
|
|
|
|
|
if ($method_id === null) {
|
|
|
|
throw new \UnexpectedValueException('Cannot get declaring method id for ' . $original_method_id);
|
|
|
|
}
|
|
|
|
|
2016-12-30 15:53:35 -05:00
|
|
|
$storage = self::getStorage($method_id);
|
|
|
|
|
|
|
|
if (!$storage) {
|
|
|
|
throw new \UnexpectedValueException('$storage should not be null');
|
|
|
|
}
|
|
|
|
|
|
|
|
list($fq_class_name) = explode('::', $method_id);
|
|
|
|
|
|
|
|
return $fq_class_name . '::' .$storage->cased_name;
|
2016-08-15 13:37:21 -04:00
|
|
|
}
|
|
|
|
|
2016-11-02 02:29:00 -04:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
2016-06-15 20:16:40 -04:00
|
|
|
public static function clearCache()
|
|
|
|
{
|
2016-12-30 15:53:35 -05:00
|
|
|
|
2016-06-15 20:16:40 -04:00
|
|
|
}
|
2016-02-04 09:22:46 -05:00
|
|
|
}
|