Skip to content

Commit 7211487

Browse files
committed
Shape object returnType model rows from their columns
1 parent 977cc28 commit 7211487

2 files changed

Lines changed: 38 additions & 6 deletions

File tree

src/Type/ModelFetchedReturnTypeHelper.php

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
2828
use PHPStan\Type\Constant\ConstantStringType;
2929
use PHPStan\Type\MixedType;
30+
use PHPStan\Type\ObjectShapeType;
3031
use PHPStan\Type\ObjectType;
3132
use PHPStan\Type\ObjectWithoutClassType;
3233
use PHPStan\Type\StringType;
@@ -51,7 +52,7 @@ public function getFetchedReturnType(ClassReflection $classReflection, ?MethodCa
5152
$returnType = $this->resolveReturnType($classReflection, $methodCall, $scope);
5253

5354
if ($returnType === 'object') {
54-
return new ObjectType(stdClass::class);
55+
return $this->resolveObjectRowType($classReflection);
5556
}
5657

5758
if ($returnType === 'array') {
@@ -85,24 +86,55 @@ private function resolveReturnType(ClassReflection $classReflection, ?MethodCall
8586
}
8687

8788
private function resolveArrayRowType(ClassReflection $classReflection): Type
89+
{
90+
$fields = $this->resolveRowFieldTypes($classReflection);
91+
92+
if ($fields === null) {
93+
return new ArrayType(new StringType(), new MixedType());
94+
}
95+
96+
$builder = ConstantArrayTypeBuilder::createEmpty();
97+
98+
foreach ($fields as $name => $type) {
99+
$builder->setOffsetValueType(new ConstantStringType($name), $type);
100+
}
101+
102+
return $builder->getArray();
103+
}
104+
105+
private function resolveObjectRowType(ClassReflection $classReflection): Type
106+
{
107+
$fields = $this->resolveRowFieldTypes($classReflection);
108+
109+
if ($fields === null) {
110+
return new ObjectType(stdClass::class);
111+
}
112+
113+
return new ObjectShapeType($fields, []);
114+
}
115+
116+
/**
117+
* @return array<string, Type>|null Null when the model's table cannot be resolved.
118+
*/
119+
private function resolveRowFieldTypes(ClassReflection $classReflection): ?array
88120
{
89121
$tableName = $classReflection->getNativeReflection()->getDefaultProperties()['table'] ?? null;
90122

91123
$table = is_string($tableName) && $tableName !== '' ? $this->schemaProvider->get()->getTable($tableName) : null;
92124

93125
if ($table === null) {
94-
return new ArrayType(new StringType(), new MixedType());
126+
return null;
95127
}
96128

97129
$casts = $this->readStringMap($classReflection, 'casts');
98130
$castHandlers = $this->readStringMap($classReflection, 'castHandlers');
99-
$builder = ConstantArrayTypeBuilder::createEmpty();
131+
$fields = [];
100132

101133
foreach ($table->columns as $column) {
102-
$builder->setOffsetValueType(new ConstantStringType($column->name), $this->resolveFieldType($column, $casts, $castHandlers));
134+
$fields[$column->name] = $this->resolveFieldType($column, $casts, $castHandlers);
103135
}
104136

105-
return $builder->getArray();
137+
return $fields;
106138
}
107139

108140
/**

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
assertType('CodeIgniter\PHPStan\Tests\Fixtures\Entity\BlogComment|null', $comments->first());
2828

2929
assertType('array{id: int, body: string|null, votes: int, payload: string|null, created_at: string|null}|null', $comments->asArray()->first());
30-
assertType('stdClass|null', $comments->asObject()->first());
30+
assertType('object{id: int, body: string|null, votes: int, payload: string|null, created_at: string|null}|null', $comments->asObject()->first());
3131

3232
$posts = new BlogPostModel();
3333

0 commit comments

Comments
 (0)