Skip to content

Commit

Permalink
Merge pull request #25 from nextras/phpstan1.0
Browse files Browse the repository at this point in the history
prepare for PHPStan 1.0
  • Loading branch information
hrach committed Nov 1, 2021
2 parents ddcf279 + a895744 commit 7c230e0
Show file tree
Hide file tree
Showing 9 changed files with 132 additions and 49 deletions.
5 changes: 4 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
],
"require": {
"php": "~7.1 || ~8.0",
"phpstan/phpstan": "^0.12.29"
"phpstan/phpstan": "^1.0"
},
"require-dev": {
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan-deprecation-rules": "^1.0",
"nextras/orm": "~4.0 || ~5.0@dev",
"nette/tester": "^2.3.1"
},
Expand All @@ -18,6 +20,7 @@
"Nextras\\OrmPhpStan\\": "src/"
}
},
"minimum-stability": "dev",
"prefer-stable": true,
"autoload-dev": {
"classmap": [
Expand Down
2 changes: 1 addition & 1 deletion extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ services:
tags:
- phpstan.rules.rule

- Nextras\OrmPhpStan\Types\Helpers\RepositoryEntityTypeHelper
- Nextras\OrmPhpStan\Types\Helpers\RepositoryEntityTypeHelper(@currentPhpVersionSimpleDirectParser)
87 changes: 84 additions & 3 deletions src/Reflection/Annotations/AnnotationPropertyReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,29 @@

namespace Nextras\OrmPhpStan\Reflection\Annotations;

use PHPStan\Reflection\Annotations\AnnotationPropertyReflection as PHPStanAnnotationPropertyReflection;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\PropertyReflection;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Type;


class AnnotationPropertyReflection extends PHPStanAnnotationPropertyReflection implements PropertyReflection
class AnnotationPropertyReflection implements PropertyReflection
{
/** @var Type */
private $writableType;

/** @var ClassReflection */
private $declaringClass;

/** @var Type */
private $readableType;

/** @var bool */
private $readable;

/** @var bool */
private $writable;


public function __construct(
ClassReflection $declaringClass,
Expand All @@ -22,8 +34,11 @@ public function __construct(
bool $writable = true
)
{
parent::__construct($declaringClass, $readableType, $readable, $writable);
$this->writableType = $writableType;
$this->declaringClass = $declaringClass;
$this->readableType = $readableType;
$this->readable = $readable;
$this->writable = $writable;
}


Expand All @@ -37,4 +52,70 @@ public function canChangeTypeAfterAssignment(): bool
{
return false;
}


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


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


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


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


public function getDocComment(): ?string
{
return null;
}


public function getReadableType(): \PHPStan\Type\Type
{
return $this->readableType;
}


public function isReadable(): bool
{
return $this->readable;
}


public function isWritable(): bool
{
return $this->writable;
}


public function isDeprecated(): \PHPStan\TrinaryLogic
{
return TrinaryLogic::createNo();
}


public function getDeprecatedDescription(): ?string
{
return null;
}


public function isInternal(): \PHPStan\TrinaryLogic
{
return TrinaryLogic::createNo();
}
}
31 changes: 12 additions & 19 deletions src/Reflection/EntityDateTimePropertyReflectionExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,21 @@

use Nextras\Orm\Entity\IEntity;
use Nextras\OrmPhpStan\Reflection\Annotations\AnnotationPropertyReflection;
use PHPStan\Reflection\Annotations\AnnotationsPropertiesClassReflectionExtension;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\PropertiesClassReflectionExtension;
use PHPStan\Reflection\PropertyReflection;
use PHPStan\ShouldNotHappenException;
use PHPStan\Type\ObjectType;
use PHPStan\Type\StringType;
use PHPStan\Type\TypeCombinator;


class EntityDateTimePropertyReflectionExtension implements PropertiesClassReflectionExtension
{
/** @var AnnotationsPropertiesClassReflectionExtension */
private $annotationsExtension;


public function __construct(AnnotationsPropertiesClassReflectionExtension $annotationsExtension)
{
$this->annotationsExtension = $annotationsExtension;
}


public function hasProperty(ClassReflection $classReflection, string $propertyName): bool
{
$hasProperty = $this->annotationsExtension->hasProperty($classReflection, $propertyName);
if (!$hasProperty) {
$property = $classReflection->getPropertyTags()[$propertyName] ?? null;
if ($property === null) {
return false;
}

Expand All @@ -39,8 +29,7 @@ public function hasProperty(ClassReflection $classReflection, string $propertyNa
return false;
}

$property = $this->annotationsExtension->getProperty($classReflection, $propertyName);
$propertyType = TypeCombinator::removeNull($property->getReadableType()); // remove null to be properly match subtype
$propertyType = TypeCombinator::removeNull($property->getType()); // remove null to properly match subtype
$dateTimeType = new ObjectType(\DateTimeImmutable::class);
$hasDateTime = $dateTimeType->isSuperTypeOf($propertyType)->yes();

Expand All @@ -50,11 +39,15 @@ public function hasProperty(ClassReflection $classReflection, string $propertyNa

public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection
{
$property = $this->annotationsExtension->getProperty($classReflection, $propertyName);
$property = $classReflection->getPropertyTags()[$propertyName] ?? null;
if ($property === null) {
throw new ShouldNotHappenException();
}

return new AnnotationPropertyReflection(
$property->getDeclaringClass(),
$property->getReadableType(),
TypeCombinator::union($property->getWritableType(), new StringType()),
$classReflection,
$property->getType(),
TypeCombinator::union($property->getType(), new StringType()),
$property->isReadable(),
$property->isWritable()
);
Expand Down
26 changes: 10 additions & 16 deletions src/Reflection/EntityRelationshipPropertyReflectionExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,19 @@

use Nextras\Orm\Entity\IEntity;
use Nextras\OrmPhpStan\Reflection\Annotations\AnnotationPropertyReflection;
use PHPStan\Reflection\Annotations\AnnotationsPropertiesClassReflectionExtension;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\PropertiesClassReflectionExtension;
use PHPStan\Reflection\PropertyReflection;
use PHPStan\ShouldNotHappenException;
use PHPStan\Type\IntegerType;
use PHPStan\Type\TypeCombinator;


class EntityRelationshipPropertyReflectionExtension implements PropertiesClassReflectionExtension
{
/** @var AnnotationsPropertiesClassReflectionExtension */
private $annotationsExtension;


public function __construct(AnnotationsPropertiesClassReflectionExtension $annotationsExtension)
{
$this->annotationsExtension = $annotationsExtension;
}


public function hasProperty(ClassReflection $classReflection, string $propertyName): bool
{
$hasProperty = $this->annotationsExtension->hasProperty($classReflection, $propertyName);
$hasProperty = array_key_exists($propertyName, $classReflection->getPropertyTags());
if (!$hasProperty) {
return false;
}
Expand All @@ -50,11 +40,15 @@ public function hasProperty(ClassReflection $classReflection, string $propertyNa

public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection
{
$property = $this->annotationsExtension->getProperty($classReflection, $propertyName);
$property = $classReflection->getPropertyTags()[$propertyName] ?? null;
if ($property === null) {
throw new ShouldNotHappenException();
}

return new AnnotationPropertyReflection(
$property->getDeclaringClass(),
$property->getReadableType(),
TypeCombinator::union($property->getWritableType(), new IntegerType()),
$classReflection,
$property->getType(),
TypeCombinator::union($property->getType(), new IntegerType()),
$property->isReadable(),
$property->isWritable()
);
Expand Down
23 changes: 18 additions & 5 deletions src/Rules/SetValueMethodRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use PHPStan\Analyser\Scope;
use PHPStan\Broker\Broker;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Rule;
use PHPStan\Type\TypeWithClassName;
use PHPStan\Type\VerbosityLevel;
Expand All @@ -17,13 +18,13 @@
*/
class SetValueMethodRule implements Rule
{
/** @var Broker */
private $broker;
/** @var ReflectionProvider */
private $reflectionProvider;


public function __construct(Broker $broker)
public function __construct(ReflectionProvider $reflectionProvider)
{
$this->broker = $broker;
$this->reflectionProvider = $reflectionProvider;
}


Expand All @@ -48,36 +49,46 @@ public function processNode(Node $node, Scope $scope): array
if (!in_array($methodName, ['setValue', 'setReadOnlyValue'], true)) {
return [];
}

$args = $node->args;
if (!isset($args[0], $args[1])) {
return [];
}
if (!$args[0] instanceof Node\Arg || !$args[1] instanceof Node\Arg) {
return [];
}

$valueType = $scope->getType($args[1]->value);
$varType = $scope->getType($node->var);
if (!$varType instanceof TypeWithClassName) {
return [];
}

$firstValue = $args[0]->value;
if (!$firstValue instanceof Node\Scalar\String_) {
return [];
}

$fieldName = $firstValue->value;
$class = $this->broker->getClass($varType->getClassName());
$class = $this->reflectionProvider->getClass($varType->getClassName());
$interfaces = array_map(function (ClassReflection $interface) {
return $interface->getName();
}, $class->getInterfaces());
if (!in_array(IEntity::class, $interfaces, true)) {
return [];
}

if (!$class->hasProperty($fieldName)) {
return [sprintf(
'Entity %s has no $%s property.',
$varType->getClassName(),
$fieldName
)];
}

$property = $class->getProperty($fieldName, $scope);
$propertyType = $property->getWritableType();

if (!$propertyType->accepts($valueType, true)->yes()) {
return [sprintf(
'Entity %s: property $%s (%s) does not accept %s.',
Expand All @@ -87,13 +98,15 @@ public function processNode(Node $node, Scope $scope): array
$valueType->describe(VerbosityLevel::typeOnly())
)];
}

if (!$property->isWritable() && $methodName !== 'setReadOnlyValue') {
return [sprintf(
'Entity %s: property $%s is read-only.',
$varType->getClassName(),
$fieldName
)];
}

return [];
}
}
2 changes: 1 addition & 1 deletion src/Types/Helpers/RepositoryEntityTypeHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ private function parseEntityClassNameTypes(ClassReflection $repositoryReflection
$className = $repositoryReflection->getName();
$fileName = $repositoryReflection->getFileName();

assert($fileName !== false, sprintf('File for clsas "%s" does not exists.', $className));
assert($fileName !== null, sprintf('File for class "%s" does not exists.', $className));

$ast = $this->parser->parseFile($fileName);

Expand Down
3 changes: 1 addition & 2 deletions src/Types/MapperMethodReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,14 @@ public function getTypeFromMethodCall(
}

$currentMapper = $this->reflectionProvider->getClass($mapper->getClassName());
assert($currentMapper !== false);

do {
$mapperClass = $currentMapper->getName();
/** @phpstan-var class-string<\Nextras\Orm\Repository\Repository> $repositoryClass */
$repositoryClass = \str_replace('Mapper', 'Repository', $mapperClass);

$currentMapper = $this->reflectionProvider->getClass($mapperClass)->getParentClass();
if ($currentMapper === false) {
if ($currentMapper === null) {
break;
}
$mapperClass = $currentMapper->getName();
Expand Down
2 changes: 1 addition & 1 deletion tests/config.neon
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
parameters:
autoload_directories:
scanDirectories:
- %rootDir%/../../../tests/testbox

services:
Expand Down

0 comments on commit 7c230e0

Please sign in to comment.