TableAssociationTypeNodeResolverExtension.php 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. <?php
  2. declare(strict_types=1);
  3. namespace Cake\PHPStan\PhpDoc;
  4. use Cake\ORM\Association;
  5. use Cake\ORM\Association\BelongsTo;
  6. use Cake\ORM\Association\BelongsToMany;
  7. use Cake\ORM\Association\HasMany;
  8. use Cake\ORM\Association\HasOne;
  9. use PHPStan\Analyser\NameScope;
  10. use PHPStan\PhpDoc\TypeNodeResolver;
  11. use PHPStan\PhpDoc\TypeNodeResolverAwareExtension;
  12. use PHPStan\PhpDoc\TypeNodeResolverExtension;
  13. use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode;
  14. use PHPStan\PhpDocParser\Ast\Type\TypeNode;
  15. use PHPStan\Type\Generic\GenericObjectType;
  16. use PHPStan\Type\ObjectType;
  17. use PHPStan\Type\Type;
  18. /**
  19. * Fix intersection association phpDoc to correct generic object type, ex:
  20. *
  21. * Change `\Cake\ORM\Association\BelongsTo&\App\Model\Table\UsersTable` to `\Cake\ORM\Association\BelongsTo<\App\Model\Table\UsersTable>`
  22. *
  23. * The type `\Cake\ORM\Association\BelongsTo&\App\Model\Table\UsersTable` is considered invalid (NeverType) by PHPStan
  24. */
  25. class TableAssociationTypeNodeResolverExtension implements TypeNodeResolverExtension, TypeNodeResolverAwareExtension
  26. {
  27. private TypeNodeResolver $typeNodeResolver;
  28. /**
  29. * @var array<string>
  30. */
  31. protected array $associationTypes = [
  32. BelongsTo::class,
  33. BelongsToMany::class,
  34. HasMany::class,
  35. HasOne::class,
  36. Association::class,
  37. ];
  38. /**
  39. * @param \PHPStan\PhpDoc\TypeNodeResolver $typeNodeResolver
  40. * @return void
  41. */
  42. public function setTypeNodeResolver(TypeNodeResolver $typeNodeResolver): void
  43. {
  44. $this->typeNodeResolver = $typeNodeResolver;
  45. }
  46. /**
  47. * @param \PHPStan\PhpDocParser\Ast\Type\TypeNode $typeNode
  48. * @param \PHPStan\Analyser\NameScope $nameScope
  49. * @return \PHPStan\Type\Type|null
  50. */
  51. public function resolve(TypeNode $typeNode, NameScope $nameScope): ?Type
  52. {
  53. if (!$typeNode instanceof IntersectionTypeNode) {
  54. return null;
  55. }
  56. $types = $this->typeNodeResolver->resolveMultiple($typeNode->types, $nameScope);
  57. $config = [
  58. 'association' => null,
  59. 'table' => null,
  60. ];
  61. foreach ($types as $type) {
  62. if (!$type instanceof ObjectType) {
  63. continue;
  64. }
  65. $className = $type->getClassName();
  66. if ($config['association'] === null && in_array($className, $this->associationTypes)) {
  67. $config['association'] = $type;
  68. } elseif ($config['table'] === null && str_ends_with($className, 'Table')) {
  69. $config['table'] = $type;
  70. }
  71. }
  72. if ($config['table'] && $config['association']) {
  73. return new GenericObjectType(
  74. $config['association']->getClassName(),
  75. [$config['table']]
  76. );
  77. }
  78. return null;
  79. }
  80. }