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