Skip to content

Commit eb763b2

Browse files
committed
Type Model findColumn as a list of the column's values
1 parent 1e2fd1d commit eb763b2

3 files changed

Lines changed: 62 additions & 2 deletions

File tree

src/Type/ModelFetchedReturnTypeHelper.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,27 @@ public function getFetchedReturnType(ClassReflection $classReflection, ?MethodCa
6767
return new ObjectWithoutClassType();
6868
}
6969

70+
/**
71+
* Resolves the type of a single column's value, as returned element-wise by `findColumn()`.
72+
*/
73+
public function getColumnFieldType(ClassReflection $classReflection, string $columnName): ?Type
74+
{
75+
$tableName = $classReflection->getNativeReflection()->getDefaultProperties()['table'] ?? null;
76+
77+
$table = is_string($tableName) && $tableName !== '' ? $this->schemaProvider->get()->getTable($tableName) : null;
78+
79+
if ($table === null) {
80+
return null;
81+
}
82+
83+
return $this->fieldType(
84+
$columnName,
85+
$table->getColumn($columnName),
86+
$this->readStringMap($classReflection, 'casts'),
87+
$this->readStringMap($classReflection, 'castHandlers'),
88+
);
89+
}
90+
7091
private function resolveReturnType(ClassReflection $classReflection, ?MethodCall $methodCall, Scope $scope): string
7192
{
7293
if ($methodCall !== null && $methodCall->hasAttribute(ModelReturnTypeTransformVisitor::RETURN_TYPE)) {

src/Type/ModelFindReturnTypeExtension.php

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,10 @@ public function getClass(): string
4242

4343
public function isMethodSupported(MethodReflection $methodReflection): bool
4444
{
45-
return in_array($methodReflection->getName(), ['find', 'findAll', 'first'], true);
45+
return in_array($methodReflection->getName(), ['find', 'findAll', 'first', 'findColumn'], true);
4646
}
4747

48-
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
48+
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type
4949
{
5050
$methodName = $methodReflection->getName();
5151

@@ -57,11 +57,44 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method
5757
return $this->getTypeFromFindAll($methodCall, $scope);
5858
}
5959

60+
if ($methodName === 'findColumn') {
61+
return $this->getTypeFromFindColumn($methodCall, $scope);
62+
}
63+
6064
$classReflection = $this->getClassReflection($methodCall, $scope);
6165

6266
return TypeCombinator::addNull($this->modelFetchedReturnTypeHelper->getFetchedReturnType($classReflection, $methodCall, $scope));
6367
}
6468

69+
private function getTypeFromFindColumn(MethodCall $methodCall, Scope $scope): ?Type
70+
{
71+
$args = $methodCall->getArgs();
72+
73+
if (! isset($args[0])) {
74+
return null;
75+
}
76+
77+
$strings = $scope->getType($args[0]->value)->getConstantStrings();
78+
79+
if (count($strings) !== 1) {
80+
return null;
81+
}
82+
83+
$fieldType = $this->modelFetchedReturnTypeHelper->getColumnFieldType(
84+
$this->getClassReflection($methodCall, $scope),
85+
$strings[0]->getValue(),
86+
);
87+
88+
if ($fieldType === null) {
89+
return null;
90+
}
91+
92+
return TypeCombinator::addNull(TypeCombinator::intersect(
93+
new ArrayType(new IntegerType(), $fieldType),
94+
new AccessoryArrayListType(),
95+
));
96+
}
97+
6598
private function getClassReflection(MethodCall $methodCall, Scope $scope): ClassReflection
6699
{
67100
$classTypes = $scope->getType($methodCall->var)->getObjectClassReflections();

tests/data/type-inference/model-return-types.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@
4848
// A selected field is still cast by the model (output name `user_id` casts via the model handler).
4949
assertType('array{user_id: CodeIgniter\PHPStan\Tests\Fixtures\Entity\Money}|null', $posts->select('user_id')->first());
5050

51+
// findColumn() returns a list of the single column's values.
52+
assertType('list<int>|null', $comments->findColumn('id'));
53+
assertType('list<string|null>|null', $comments->findColumn('body'));
54+
assertType('list<CodeIgniter\PHPStan\Tests\Fixtures\Entity\Money>|null', $posts->findColumn('user_id'));
55+
assertType('list<CodeIgniter\I18n\Time>|null', $posts->findColumn('title'));
56+
5157
function selectDynamically(BlogCommentModel $model, string $columns): void
5258
{
5359
assertType('array<string, mixed>|null', $model->select($columns)->asArray()->first());

0 commit comments

Comments
 (0)