Skip to content

Commit 57da04e

Browse files
committed
Layer model casts over column types in array row shapes
1 parent c83bf55 commit 57da04e

3 files changed

Lines changed: 47 additions & 5 deletions

File tree

src/Type/ModelFetchedReturnTypeHelper.php

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
namespace CodeIgniter\PHPStan\Type;
1515

16+
use CodeIgniter\PHPStan\Database\Schema\CastTypeResolver;
17+
use CodeIgniter\PHPStan\Database\Schema\Column;
1618
use CodeIgniter\PHPStan\Database\Schema\ColumnTypeResolver;
1719
use CodeIgniter\PHPStan\Database\SchemaProvider;
1820
use CodeIgniter\PHPStan\NodeVisitor\ModelReturnTypeTransformVisitor;
@@ -33,14 +35,15 @@
3335

3436
/**
3537
* Resolves the type of a single fetched row for a model, honoring its `$returnType` (or the
36-
* `asArray()`/`asObject()` override) and shaping array rows from the live table columns.
38+
* `asArray()`/`asObject()` override) and shaping array rows from the live columns and `$casts`.
3739
*/
3840
final class ModelFetchedReturnTypeHelper
3941
{
4042
public function __construct(
4143
private readonly ReflectionProvider $reflectionProvider,
4244
private readonly SchemaProvider $schemaProvider,
4345
private readonly ColumnTypeResolver $columnTypeResolver,
46+
private readonly CastTypeResolver $castTypeResolver,
4447
) {}
4548

4649
public function getFetchedReturnType(ClassReflection $classReflection, ?MethodCall $methodCall, Scope $scope): Type
@@ -91,12 +94,47 @@ private function resolveArrayRowType(ClassReflection $classReflection): Type
9194
return new ArrayType(new StringType(), new MixedType());
9295
}
9396

97+
$casts = $this->readStringMap($classReflection, 'casts');
9498
$builder = ConstantArrayTypeBuilder::createEmpty();
9599

96100
foreach ($table->columns as $column) {
97-
$builder->setOffsetValueType(new ConstantStringType($column->name), $this->columnTypeResolver->resolve($column));
101+
$builder->setOffsetValueType(new ConstantStringType($column->name), $this->resolveFieldType($column, $casts));
98102
}
99103

100104
return $builder->getArray();
101105
}
106+
107+
/**
108+
* @param array<string, string> $casts
109+
*/
110+
private function resolveFieldType(Column $column, array $casts): Type
111+
{
112+
if (isset($casts[$column->name])) {
113+
return $this->castTypeResolver->resolve($casts[$column->name]) ?? new MixedType();
114+
}
115+
116+
return $this->columnTypeResolver->resolve($column);
117+
}
118+
119+
/**
120+
* @return array<string, string>
121+
*/
122+
private function readStringMap(ClassReflection $classReflection, string $property): array
123+
{
124+
$value = $classReflection->getNativeReflection()->getDefaultProperties()[$property] ?? [];
125+
126+
if (! is_array($value)) {
127+
return [];
128+
}
129+
130+
$map = [];
131+
132+
foreach ($value as $key => $cast) {
133+
if (is_string($key) && is_string($cast)) {
134+
$map[$key] = $cast;
135+
}
136+
}
137+
138+
return $map;
139+
}
102140
}

tests/Fixtures/Models/BlogPostModel.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,9 @@
1717

1818
final class BlogPostModel extends Model
1919
{
20-
protected $table = 'blog_posts';
20+
protected $table = 'blog_posts';
21+
protected array $casts = [
22+
'user_id' => 'string',
23+
'title' => 'datetime',
24+
];
2125
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,5 @@
3131

3232
$posts = new BlogPostModel();
3333

34-
assertType('array{id: int, user_id: int, title: string}|null', $posts->first());
35-
assertType('list<array{id: int, user_id: int, title: string}>', $posts->findAll());
34+
assertType('array{id: int, user_id: string, title: CodeIgniter\I18n\Time}|null', $posts->first());
35+
assertType('list<array{id: int, user_id: string, title: CodeIgniter\I18n\Time}>', $posts->findAll());

0 commit comments

Comments
 (0)