From 6ac3f145845d716ba4a1c122bc926b875708af68 Mon Sep 17 00:00:00 2001 From: Adam Halfar Date: Mon, 14 Oct 2024 14:54:27 +0200 Subject: [PATCH] Add TableAssociationTypeNodeResolverExtension file and load it automatically --- extension.neon | 4 + ...leAssociationTypeNodeResolverExtension.php | 89 +++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 src/PhpDoc/TableAssociationTypeNodeResolverExtension.php diff --git a/extension.neon b/extension.neon index 0e1209f..b162a2b 100644 --- a/extension.neon +++ b/extension.neon @@ -73,3 +73,7 @@ services: factory: CakeDC\PHPStan\Type\ShellHelperLoadDynamicReturnTypeExtension(Cake\Console\ConsoleIo) tags: - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: CakeDC\PHPStan\PhpDoc\TableAssociationTypeNodeResolverExtension + tags: + - phpstan.phpDoc.typeNodeResolverExtension \ No newline at end of file diff --git a/src/PhpDoc/TableAssociationTypeNodeResolverExtension.php b/src/PhpDoc/TableAssociationTypeNodeResolverExtension.php new file mode 100644 index 0000000..9c73688 --- /dev/null +++ b/src/PhpDoc/TableAssociationTypeNodeResolverExtension.php @@ -0,0 +1,89 @@ +` + * + * The type `\Cake\ORM\Association\BelongsTo&\App\Model\Table\UsersTable` is considered invalid (NeverType) by PHPStan + */ +class TableAssociationTypeNodeResolverExtension implements TypeNodeResolverExtension, TypeNodeResolverAwareExtension +{ + private TypeNodeResolver $typeNodeResolver; + + /** + * @var array + */ + protected array $associationTypes = [ + BelongsTo::class, + BelongsToMany::class, + HasMany::class, + HasOne::class, + Association::class, + ]; + + /** + * @param \PHPStan\PhpDoc\TypeNodeResolver $typeNodeResolver + * @return void + */ + public function setTypeNodeResolver(TypeNodeResolver $typeNodeResolver): void + { + $this->typeNodeResolver = $typeNodeResolver; + } + + /** + * @param \PHPStan\PhpDocParser\Ast\Type\TypeNode $typeNode + * @param \PHPStan\Analyser\NameScope $nameScope + * @return \PHPStan\Type\Type|null + */ + public function resolve(TypeNode $typeNode, NameScope $nameScope): ?Type + { + if (!$typeNode instanceof IntersectionTypeNode) { + return null; + } + $types = $this->typeNodeResolver->resolveMultiple($typeNode->types, $nameScope); + $config = [ + 'association' => null, + 'table' => null, + ]; + foreach ($types as $type) { + if (!$type instanceof ObjectType) { + continue; + } + $className = $type->getClassName(); + if ($config['association'] === null && in_array($className, $this->associationTypes)) { + $config['association'] = $type; + } elseif ($config['table'] === null && str_ends_with($className, 'Table')) { + $config['table'] = $type; + } + } + if ($config['table'] && $config['association']) { + return new GenericObjectType( + $config['association']->getClassName(), + [$config['table']] + ); + } + + return null; + } +}