Skip to content

Commit

Permalink
Support for class constant visibility
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Dec 4, 2016
1 parent ee8e316 commit 67c4046
Show file tree
Hide file tree
Showing 10 changed files with 310 additions and 5 deletions.
12 changes: 11 additions & 1 deletion build.xml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,16 @@
</target>

<target name="phpstan">
<php expression="PHP_VERSION_ID &lt; 70100 ?'true':'false'" returnProperty="isPHP70" level="verbose" />
<if>
<equals arg1="${isPHP70}" arg2="true" />
<then>
<property name="phpstan.config" value="phpstan.php7.0.neon"/>
</then>
<else>
<property name="phpstan.config" value="phpstan.neon"/>
</else>
</if>
<exec
executable="bin/phpstan"
logoutput="true"
Expand All @@ -96,7 +106,7 @@
>
<arg value="analyse"/>
<arg value="-c"/>
<arg path="phpstan.neon"/>
<arg path="${phpstan.config}"/>
<arg value="-l"/>
<arg value="4"/>
<arg path="src"/>
Expand Down
8 changes: 8 additions & 0 deletions phpstan.php7.0.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
includes:
- phpstan.neon

parameters:
ignoreErrors:
- '#has unknown class ReflectionClassConstant as its type#'
- '#has invalid typehint type ReflectionClassConstant#'
- '#Call to an undefined method ReflectionClass::getReflectionConstant#'
10 changes: 8 additions & 2 deletions src/Analyser/Scope.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Scalar\String_;
use PHPStan\Broker\Broker;
use PHPStan\Reflection\ClassConstantReflection;
use PHPStan\Reflection\ClassMemberReflection;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\MethodReflection;
Expand Down Expand Up @@ -418,8 +419,8 @@ public function getType(Node $node): Type
if ($this->broker->hasClass($constantClass)) {
$constantClassReflection = $this->broker->getClass($constantClass);
if ($constantClassReflection->hasConstant($constantName)) {
$constantValue = $constantClassReflection->getNativeReflection()->getConstant($constantName);
$typeFromValue = $this->getTypeFromValue($constantValue);
$constant = $constantClassReflection->getConstant($constantName);
$typeFromValue = $this->getTypeFromValue($constant->getValue());
if ($typeFromValue !== null) {
return $typeFromValue;
}
Expand Down Expand Up @@ -1185,6 +1186,11 @@ public function canCallMethod(MethodReflection $methodReflection): bool
return $this->canAccessClassMember($methodReflection);
}

public function canAccessConstant(ClassConstantReflection $constantReflection): bool
{
return $this->canAccessClassMember($constantReflection);
}

private function canAccessClassMember(ClassMemberReflection $classMemberReflection): bool
{
if ($classMemberReflection->isPublic()) {
Expand Down
15 changes: 15 additions & 0 deletions src/Reflection/ClassConstantReflection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php declare(strict_types = 1);

namespace PHPStan\Reflection;

interface ClassConstantReflection extends ClassMemberReflection
{

public function getName(): string;

/**
* @return mixed
*/
public function getValue();

}
56 changes: 56 additions & 0 deletions src/Reflection/ClassConstantWithVisibilityReflection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php declare(strict_types = 1);

namespace PHPStan\Reflection;

class ClassConstantWithVisibilityReflection implements ClassConstantReflection
{

/** @var \PHPStan\Reflection\ClassReflection */
private $declaringClass;

/** @var \ReflectionClassConstant */
private $reflection;

public function __construct(
ClassReflection $declaringClass,
\ReflectionClassConstant $reflection
)
{
$this->declaringClass = $declaringClass;
$this->reflection = $reflection;
}

public function getName(): string
{
return $this->reflection->getName();
}

/**
* @return mixed
*/
public function getValue()
{
return $this->reflection->getValue();
}

public function getDeclaringClass(): ClassReflection
{
return $this->declaringClass;
}

public function isStatic(): bool
{
return true;
}

public function isPrivate(): bool
{
return $this->reflection->isPrivate();
}

public function isPublic(): bool
{
return $this->reflection->isPublic();
}

}
23 changes: 23 additions & 0 deletions src/Reflection/ClassReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ class ClassReflection
/** @var \PHPStan\Reflection\PropertyReflection[] */
private $properties = [];

/** @var \PHPStan\Reflection\ClassConstantReflection[] */
private $constants;

public function __construct(
Broker $broker,
array $propertiesClassReflectionExtensions,
Expand Down Expand Up @@ -188,6 +191,26 @@ public function hasConstant(string $name): bool
return $this->getNativeReflection()->hasConstant($name);
}

public function getConstant(string $name): ClassConstantReflection
{
if (!isset($this->constants[$name])) {
if (PHP_VERSION_ID < 70100) {
$this->constants[$name] = new ObsoleteClassConstantReflection(
$this,
$name,
$this->getNativeReflection()->getConstant($name)
);
} else {
$reflectionConstant = $this->getNativeReflection()->getReflectionConstant($name);
$this->constants[$name] = new ClassConstantWithVisibilityReflection(
$this->broker->getClass($reflectionConstant->getDeclaringClass()->getName()),
$reflectionConstant
);
}
}
return $this->constants[$name];
}

public function hasTraitUse(string $traitName): bool
{
return in_array($traitName, $this->getTraitNames(), true);
Expand Down
66 changes: 66 additions & 0 deletions src/Reflection/ObsoleteClassConstantReflection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php declare(strict_types = 1);

namespace PHPStan\Reflection;

class ObsoleteClassConstantReflection implements ClassConstantReflection
{

/** @var \PHPStan\Reflection\ClassReflection */
private $declaringClass;

/** @var string */
private $name;

/** @var mixed */
private $value;

/**
* @param \PHPStan\Reflection\ClassReflection $declaringClass
* @param string $name
* @param mixed $value
*/
public function __construct(
ClassReflection $declaringClass,
string $name,
$value
)
{
$this->declaringClass = $declaringClass;
$this->name = $name;
$this->value = $value;
}

public function getName(): string
{
return $this->name;
}

/**
* @return mixed
*/
public function getValue()
{
return $this->value;
}

public function getDeclaringClass(): ClassReflection
{
return $this->declaringClass;
}

public function isStatic(): bool
{
return true;
}

public function isPrivate(): bool
{
return false;
}

public function isPublic(): bool
{
return true;
}

}
24 changes: 23 additions & 1 deletion src/Rules/Classes/ClassConstantRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,28 @@ public function processNode(Node $node, Scope $scope): array
}
}

$constantName = $node->name;
if ($scope->getClass() !== null && $className === 'parent') {
$currentClassReflection = $this->broker->getClass($scope->getClass());
if ($currentClassReflection->getParentClass() === false) {
return [
sprintf(
'Access to parent::%s but %s does not extend any class.',
$constantName,
$scope->getClass()
),
];
}

$className = $currentClassReflection->getParentClass()->getName();
}

if (!$this->broker->hasClass($className)) {
return [
sprintf('Class %s not found.', $className),
];
}

$constantName = $node->name;
if ($constantName === 'class') {
return [];
}
Expand All @@ -78,6 +93,13 @@ public function processNode(Node $node, Scope $scope): array
];
}

$constantReflection = $classReflection->getConstant($constantName);
if (!$scope->canAccessConstant($constantReflection)) {
return [
sprintf('Cannot access constant %s::%s from current scope.', $constantReflection->getDeclaringClass()->getName(), $constantName),
];
}

return [];
}

Expand Down
35 changes: 34 additions & 1 deletion tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ protected function getRule(): Rule
return new ClassConstantRule($this->createBroker());
}

public function testClassDoesNotExist()
public function testClassConstant()
{
$this->analyse(
[
Expand Down Expand Up @@ -40,4 +40,37 @@ public function testClassDoesNotExist()
);
}

/**
* @requires PHP 7.1
*/
public function testClassConstantVisibility()
{
$this->analyse([__DIR__ . '/data/class-constant-visibility.php'], [
[
'Cannot access constant ClassConstantVisibility\Bar::PRIVATE_BAR from current scope.',
25,
],
[
'Access to parent::BAZ but ClassConstantVisibility\Foo does not extend any class.',
27,
],
[
'Access to undefined constant ClassConstantVisibility\Bar::PRIVATE_FOO.',
45,
],
[
'Cannot access constant ClassConstantVisibility\Foo::PRIVATE_FOO from current scope.',
46,
],
[
'Cannot access constant ClassConstantVisibility\Foo::PRIVATE_FOO from current scope.',
47,
],
[
'Cannot access constant ClassConstantVisibility\Foo::PROTECTED_FOO from current scope.',
63,
],
]);
}

}

0 comments on commit 67c4046

Please sign in to comment.