1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-14 02:07:37 +01:00
psalm/src/Psalm/Checker/ClassChecker.php

222 lines
6.3 KiB
PHP
Raw Normal View History

<?php
namespace Psalm\Checker;
use PhpParser;
use Psalm\StatementsSource;
use Psalm\Context;
class ClassChecker extends ClassLikeChecker
{
2016-10-10 07:35:12 +02:00
/**
* @var PhpParser\Node\Stmt\Class_
*/
protected $class;
2016-09-09 15:13:41 +02:00
/**
* A lookup table of existing classes
* @var array
*/
protected static $existing_classes = [];
2016-09-09 15:13:41 +02:00
/**
* A lookup table of existing classes, all lowercased
* @var array
*/
protected static $existing_classes_ci = [];
2016-09-09 15:13:41 +02:00
/**
* A lookup table used for caching the results of classExtends calls
* @var array
*/
protected static $class_extends = [];
2016-10-31 20:42:20 +01:00
/**
* @var integer
*/
2016-10-21 02:54:17 +02:00
protected static $anonymous_class_count = 0;
2016-09-09 15:13:41 +02:00
/**
* @param PhpParser\Node\Stmt\Class_ $class
* @param StatementsSource $source
2016-10-21 02:54:17 +02:00
* @param string|null $absolute_class
2016-09-09 15:13:41 +02:00
*/
2016-10-23 07:57:11 +02:00
public function __construct(PhpParser\Node\Stmt\ClassLike $class, StatementsSource $source, $absolute_class)
{
2016-10-23 07:57:11 +02:00
if (!$class instanceof PhpParser\Node\Stmt\Class_) {
throw new \InvalidArgumentException('Bad');
}
2016-10-21 02:54:17 +02:00
if ($absolute_class === null) {
$absolute_class = 'PsalmAnonymousClass' . (self::$anonymous_class_count++);
}
parent::__construct($class, $source, $absolute_class);
self::$existing_classes[$absolute_class] = true;
self::$existing_classes_ci[strtolower($absolute_class)] = true;
self::$class_implements[$absolute_class] = [];
if ($this->class->extends) {
$this->parent_class = self::getAbsoluteClassFromName($this->class->extends, $this->namespace, $this->aliased_classes);
}
foreach ($class->implements as $interface_name) {
$absolute_interface_name = self::getAbsoluteClassFromName($interface_name, $this->namespace, $this->aliased_classes);
self::$class_implements[$absolute_class][strtolower($absolute_interface_name)] = $absolute_interface_name;
}
}
2016-09-09 15:13:41 +02:00
/**
* Determine whether or not a given class exists
*
* @param string $absolute_class
* @return bool
*/
public static function classExists($absolute_class)
{
if (isset(self::$existing_classes_ci[strtolower($absolute_class)])) {
return self::$existing_classes_ci[strtolower($absolute_class)];
}
if (in_array($absolute_class, self::$SPECIAL_TYPES)) {
return false;
}
2016-10-30 06:13:33 +01:00
$old_level = error_reporting();
error_reporting(0);
$class_exists = class_exists($absolute_class);
error_reporting($old_level);
if ($class_exists) {
$old_level = error_reporting();
error_reporting(0);
$reflected_class = new \ReflectionClass($absolute_class);
2016-10-30 06:13:33 +01:00
error_reporting($old_level);
self::$existing_classes_ci[strtolower($absolute_class)] = true;
self::$existing_classes[$reflected_class->getName()] = true;
return true;
}
2016-08-14 19:00:41 +02:00
// we can only be sure that the case-sensitive version does not exist
self::$existing_classes[$absolute_class] = false;
return false;
}
2016-09-09 15:13:41 +02:00
/**
* Determine whether or not a class has the correct casing
*
* @param string $absolute_class
* @return bool
*/
public static function hasCorrectCasing($absolute_class)
{
if (!self::classExists($absolute_class)) {
throw new \InvalidArgumentException('Cannot check casing on nonexistent class ' . $absolute_class);
}
return isset(self::$existing_classes[$absolute_class]);
}
/**
2016-09-09 15:13:41 +02:00
* Determine whether or not a class extends a parent
*
* @param string $absolute_class
* @param string $possible_parent
* @return bool
*/
public static function classExtends($absolute_class, $possible_parent)
{
if (isset(self::$class_extends[$absolute_class][$possible_parent])) {
return self::$class_extends[$absolute_class][$possible_parent];
}
if (!self::classExists($absolute_class) || !self::classExists($possible_parent)) {
return false;
}
if (!isset(self::$class_extends[$absolute_class])) {
self::$class_extends[$absolute_class] = [];
}
self::$class_extends[$absolute_class][$possible_parent] = is_subclass_of($absolute_class, $possible_parent);
return self::$class_extends[$absolute_class][$possible_parent];
}
2016-09-09 15:13:41 +02:00
/**
* Get all the interfaces a given class implements
*
* @param string $absolute_class
* @return array<string>
*/
public static function getInterfacesForClass($absolute_class)
{
if (!isset(self::$class_implements[$absolute_class])) {
2016-10-12 07:38:29 +02:00
/** @var string[] */
$class_implements = class_implements($absolute_class);
self::$class_implements[$absolute_class] = [];
foreach ($class_implements as $interface) {
self::$class_implements[$absolute_class][strtolower($interface)] = $interface;
}
}
return self::$class_implements[$absolute_class];
}
/**
2016-09-09 15:13:41 +02:00
* Check whether a class implements an interface
*
* @param string $absolute_class
* @param string $interface
* @return bool
*/
public static function classImplements($absolute_class, $interface)
{
$interface_id = strtolower($interface);
2016-10-21 00:05:28 +02:00
if ($interface_id === 'callable' && $absolute_class === 'Closure') {
return true;
}
if (isset(self::$class_implements[$absolute_class][$interface_id])) {
return true;
}
if (isset(self::$class_implements[$absolute_class])) {
return false;
}
if (!ClassChecker::classExists($absolute_class)) {
return false;
}
if (in_array($interface_id, self::$SPECIAL_TYPES)) {
return false;
}
$class_implementations = self::getInterfacesForClass($absolute_class);
return isset($class_implementations[$interface_id]);
}
2016-08-15 17:01:50 +02:00
public static function clearCache()
{
self::$existing_classes = [];
self::$existing_classes_ci = [];
self::$class_extends = [];
2016-10-21 02:54:17 +02:00
self::$anonymous_class_count = 0;
MethodChecker::clearCache();
2016-08-15 17:01:50 +02:00
}
}