Skip to content

Commit

Permalink
Do not use instanceof *Type
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Feb 18, 2023
1 parent 05db8be commit 633cccd
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 137 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
use PHPStan\Type\IntegerType;
use PHPStan\Type\NullType;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeWithClassName;
use function lcfirst;
use function str_replace;
use function strlen;
Expand Down Expand Up @@ -54,10 +53,7 @@ public function hasMethod(ClassReflection $classReflection, string $methodName):

$repositoryAncesor = $classReflection->getAncestorWithClassName(ObjectRepository::class);
if ($repositoryAncesor === null) {
$repositoryAncesor = $classReflection->getAncestorWithClassName(ObjectRepository::class);
if ($repositoryAncesor === null) {
return false;
}
return false;
}

$templateTypeMap = $repositoryAncesor->getActiveTemplateTypeMap();
Expand All @@ -66,22 +62,22 @@ public function hasMethod(ClassReflection $classReflection, string $methodName):
return false;
}

if (!$entityClassType instanceof TypeWithClassName) {
return false;
}
$entityClassNames = $entityClassType->getObjectClassNames();
$fieldName = $this->classify($methodFieldName);

$classReflection = $entityClassType->getClassReflection();
if ($classReflection === null) {
return false;
}
/** @var class-string $entityClassName */
foreach ($entityClassNames as $entityClassName) {
$classMetadata = $this->objectMetadataResolver->getClassMetadata($entityClassName);
if ($classMetadata === null) {
continue;
}

$fieldName = $this->classify($methodFieldName);
$classMetadata = $this->objectMetadataResolver->getClassMetadata($classReflection->getName());
if ($classMetadata === null) {
return false;
if ($classMetadata->hasField($fieldName) || $classMetadata->hasAssociation($fieldName)) {
return true;
}
}

return $classMetadata->hasField($fieldName) || $classMetadata->hasAssociation($fieldName);
return false;
}

private function classify(string $word): string
Expand Down
6 changes: 2 additions & 4 deletions src/Rules/Doctrine/ORM/EntityColumnRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,15 +160,13 @@ public function processNode(Node $node, Scope $scope): array
return [];
}

$transformArrays = static function (Type $type, callable $traverse): Type {
$propertyTransformedType = TypeTraverser::map($propertyType, static function (Type $type, callable $traverse): Type {
if ($type instanceof ArrayType) {
return new ArrayType(new MixedType(), new MixedType());
}

return $traverse($type);
};

$propertyTransformedType = TypeTraverser::map($propertyType, $transformArrays);
});

if (!$propertyTransformedType->isSuperTypeOf($writableToPropertyType)->yes()) {
$errors[] = sprintf(
Expand Down
67 changes: 23 additions & 44 deletions src/Rules/Doctrine/ORM/RepositoryMethodCallRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@
use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\Doctrine\ObjectMetadataResolver;
use PHPStan\Type\GenericTypeVariableResolver;
use PHPStan\Type\TypeWithClassName;
use PHPStan\Type\VerbosityLevel;
use function count;
use function in_array;
Expand Down Expand Up @@ -41,28 +37,12 @@ public function processNode(Node $node, Scope $scope): array
return [];
}
$argType = $scope->getType($node->getArgs()[0]->value);
if (!$argType instanceof ConstantArrayType) {
return [];
}
if (count($argType->getKeyTypes()) === 0) {
return [];
}
$calledOnType = $scope->getType($node->var);
if (!$calledOnType instanceof TypeWithClassName) {
return [];
}
$entityClassType = GenericTypeVariableResolver::getType($calledOnType, ObjectRepository::class, 'TEntityClass');
if ($entityClassType === null) {
$entityClassType = GenericTypeVariableResolver::getType($calledOnType, ObjectRepository::class, 'TEntityClass');
if ($entityClassType === null) {
return [];
}
}
if (!$entityClassType instanceof TypeWithClassName) {
return [];
}
$entityClassReflection = $entityClassType->getClassReflection();
if ($entityClassReflection === null) {
$entityClassType = $calledOnType->getTemplateType(ObjectRepository::class, 'TEntityClass');

/** @var list<class-string> $entityClassNames */
$entityClassNames = $entityClassType->getObjectClassNames();
if (count($entityClassNames) !== 1) {
return [];
}

Expand All @@ -80,32 +60,31 @@ public function processNode(Node $node, Scope $scope): array
return [];
}

$classMetadata = $this->objectMetadataResolver->getClassMetadata($entityClassReflection->getName());
$classMetadata = $this->objectMetadataResolver->getClassMetadata($entityClassNames[0]);
if ($classMetadata === null) {
return [];
}

$messages = [];
foreach ($argType->getKeyTypes() as $keyType) {
if (!$keyType instanceof ConstantStringType) {
continue;
}
foreach ($argType->getConstantArrays() as $constantArray) {
foreach ($constantArray->getKeyTypes() as $keyType) {
foreach ($keyType->getConstantStrings() as $fieldName) {
if (
$classMetadata->hasField($fieldName->getValue())
|| $classMetadata->hasAssociation($fieldName->getValue())
) {
continue;
}

$fieldName = $keyType->getValue();
if (
$classMetadata->hasField($fieldName)
|| $classMetadata->hasAssociation($fieldName)
) {
continue;
$messages[] = sprintf(
'Call to method %s::%s() - entity %s does not have a field named $%s.',
$calledOnType->describe(VerbosityLevel::typeOnly()),
$methodName,
$entityClassNames[0],
$fieldName->getValue()
);
}
}

$messages[] = sprintf(
'Call to method %s::%s() - entity %s does not have a field named $%s.',
$calledOnType->describe(VerbosityLevel::typeOnly()),
$methodName,
$entityClassReflection->getDisplayName(),
$fieldName
);
}

return $messages;
Expand Down
8 changes: 4 additions & 4 deletions src/Type/Doctrine/ArgumentsProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
use PhpParser\Node\Arg;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Doctrine\ORM\DynamicQueryBuilderArgumentException;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\ConstantScalarType;
use PHPStan\Type\Doctrine\QueryBuilder\Expr\ExprType;
use function count;
use function strpos;

/** @api */
Expand Down Expand Up @@ -35,10 +35,10 @@ public function processArgs(
$args[] = $value->getExprObject();
continue;
}
if ($value instanceof ConstantArrayType) {
if (count($value->getConstantArrays()) === 1) {
$array = [];
foreach ($value->getKeyTypes() as $i => $keyType) {
$valueType = $value->getValueTypes()[$i];
foreach ($value->getConstantArrays()[0]->getKeyTypes() as $i => $keyType) {
$valueType = $value->getConstantArrays()[0]->getValueTypes()[$i];
if (!$valueType instanceof ConstantScalarType) {
throw new DynamicQueryBuilderArgumentException();
}
Expand Down
36 changes: 16 additions & 20 deletions src/Type/Doctrine/GetRepositoryDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,19 @@
use Doctrine\ODM\MongoDB\Repository\DocumentRepository;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Mapping\MappingException;
use Doctrine\Persistence\ObjectRepository;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\Generic\GenericClassStringType;
use PHPStan\Type\ErrorType;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\ObjectWithoutClassType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeWithClassName;
use function count;

class GetRepositoryDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
Expand Down Expand Up @@ -91,25 +90,21 @@ public function getTypeFromMethodCall(
);
}
$argType = $scope->getType($methodCall->getArgs()[0]->value);
if ($argType instanceof ConstantStringType) {
$objectName = $argType->getValue();
$classType = new ObjectType($objectName);
} elseif ($argType instanceof GenericClassStringType) {
$classType = $argType->getGenericType();
if (!$classType instanceof TypeWithClassName) {
return new GenericObjectType(
$defaultRepositoryClass,
[$classType]
);
}

$objectName = $classType->getClassName();
} else {
if (!$argType->isClassStringType()->yes()) {
return $this->getDefaultReturnType($scope, $methodCall->getArgs(), $methodReflection, $defaultRepositoryClass);
}

$classType = $argType->getClassStringObjectType();
$objectNames = $classType->getObjectClassNames();
if (count($objectNames) !== 1) {
return new GenericObjectType(
$defaultRepositoryClass,
[$classType]
);
}

try {
$repositoryClass = $this->getRepositoryClass($objectName, $defaultRepositoryClass);
$repositoryClass = $this->getRepositoryClass($objectNames[0], $defaultRepositoryClass);
} catch (\Doctrine\Persistence\Mapping\MappingException | MappingException | AnnotationException $e) {
return $this->getDefaultReturnType($scope, $methodCall->getArgs(), $methodReflection, $defaultRepositoryClass);
}
Expand All @@ -129,10 +124,11 @@ private function getDefaultReturnType(Scope $scope, array $args, MethodReflectio
$args,
$methodReflection->getVariants()
)->getReturnType();
if ($defaultType instanceof GenericObjectType && count($defaultType->getTypes()) > 0) {
$entity = $defaultType->getTemplateType(ObjectRepository::class, 'TEntityClass');
if (!$entity instanceof ErrorType) {
return new GenericObjectType(
$defaultRepositoryClass,
[$defaultType->getTypes()[0]]
[$entity]
);
}

Expand Down
41 changes: 4 additions & 37 deletions src/Type/Doctrine/Query/QueryResultDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,11 @@
use PHPStan\Type\ArrayType;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\GenericTypeVariableResolver;
use PHPStan\Type\IntegerType;
use PHPStan\Type\IterableType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NullType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeWithClassName;
use PHPStan\Type\VoidType;

final class QueryResultDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
Expand Down Expand Up @@ -71,45 +68,15 @@ public function getTypeFromMethodCall(
}

$queryType = $scope->getType($methodCall->var);
$queryResultType = $this->getQueryResultType($queryType);
$queryKeyType = $this->getQueryKeyType($queryType);

return $this->getMethodReturnTypeForHydrationMode(
$methodReflection,
$hydrationMode,
$queryKeyType,
$queryResultType
$queryType->getTemplateType(AbstractQuery::class, 'TKey'),
$queryType->getTemplateType(AbstractQuery::class, 'TResult')
);
}

private function getQueryResultType(Type $queryType): Type
{
if (!$queryType instanceof TypeWithClassName) {
return new MixedType();
}

$resultType = GenericTypeVariableResolver::getType($queryType, AbstractQuery::class, 'TResult');
if ($resultType === null) {
return new MixedType();
}

return $resultType;
}

private function getQueryKeyType(Type $queryType): Type
{
if (!$queryType instanceof TypeWithClassName) {
return new MixedType();
}

$resultType = GenericTypeVariableResolver::getType($queryType, AbstractQuery::class, 'TKey');
if ($resultType === null) {
return new MixedType();
}

return $resultType;
}

private function getMethodReturnTypeForHydrationMode(
MethodReflection $methodReflection,
Type $hydrationMode,
Expand Down Expand Up @@ -144,11 +111,11 @@ private function getMethodReturnTypeForHydrationMode(
return TypeCombinator::addNull($queryResultType);
case 'toIterable':
return new IterableType(
$queryKeyType instanceof NullType ? new IntegerType() : $queryKeyType,
$queryKeyType->isNull()->yes() ? new IntegerType() : $queryKeyType,
$queryResultType
);
default:
if ($queryKeyType instanceof NullType) {
if ($queryKeyType->isNull()->yes()) {
return AccessoryArrayListType::intersectWith(new ArrayType(
new IntegerType(),
$queryResultType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\Generic\GenericClassStringType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeWithClassName;
use function array_unshift;
use function count;

class EntityRepositoryCreateQueryBuilderDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
{
Expand All @@ -37,8 +36,8 @@ public function getTypeFromMethodCall(
$entityNameExpr = new MethodCall($methodCall->var, new Identifier('getEntityName'));

$entityNameExprType = $scope->getType($entityNameExpr);
if ($entityNameExprType instanceof GenericClassStringType && $entityNameExprType->getGenericType() instanceof TypeWithClassName) {
$entityNameExpr = new String_($entityNameExprType->getGenericType()->getClassName());
if ($entityNameExprType->isClassStringType()->yes() && count($entityNameExprType->getClassStringObjectType()->getObjectClassNames()) === 1) {
$entityNameExpr = new String_($entityNameExprType->getClassStringObjectType()->getObjectClassNames()[0]);
}

if (!isset($methodCall->getArgs()[0])) {
Expand Down
Loading

0 comments on commit 633cccd

Please sign in to comment.