From c38fa352e5047e4259e33557ced381885224b4ba Mon Sep 17 00:00:00 2001 From: Kyle Date: Fri, 17 May 2024 16:17:17 +0200 Subject: [PATCH 1/2] Improve inferring model property types, based on DB driver --- .../InferExtensions/ModelExtension.php | 42 ++++++++++++------- src/Support/ResponseExtractor/ModelInfo.php | 2 + 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/Support/InferExtensions/ModelExtension.php b/src/Support/InferExtensions/ModelExtension.php index 43cf134f..912a75fc 100644 --- a/src/Support/InferExtensions/ModelExtension.php +++ b/src/Support/InferExtensions/ModelExtension.php @@ -56,7 +56,7 @@ public function getPropertyType(PropertyFetchEvent $event): ?Type if ($attribute = $info->get('attributes')->get($event->getName())) { $baseType = $this->getAttributeTypeFromEloquentCasts($attribute['cast'] ?? '') - ?? $this->getAttributeTypeFromDbColumnType($attribute['type'] ?? ''); + ?? $this->getAttributeTypeFromDbColumnType($attribute['type'], $attribute['driver']); if ($attribute['nullable']) { return Union::wrap([$baseType, new NullType()]); @@ -72,22 +72,34 @@ public function getPropertyType(PropertyFetchEvent $event): ?Type throw new \LogicException('Should not happen'); } - private function getAttributeTypeFromDbColumnType(string $columnType): AbstractType + /** + * MySQL/MariaDB decimal is mapped to a string by PDO. + * Floating point numbers and decimals are all mapped to strings when using the pgsql driver. + */ + private function getAttributeTypeFromDbColumnType(?string $columnType, ?string $dbDriverName): AbstractType { - $type = Str::before($columnType, ' '); - $typeName = Str::before($type, '('); - - // @todo Fix to native types - $attributeType = match ($typeName) { - 'int', 'integer', 'bigint' => new IntegerType(), - 'float', 'double', 'decimal' => new FloatType(), - 'varchar', 'string', 'text', 'datetime' => new StringType(), // string, text - needed? - 'tinyint', 'bool', 'boolean' => new BooleanType(), // bool, boolean - needed? - 'json', 'array' => new ArrayType(), - default => new UnknownType("unimplemented DB column type [$type]"), - }; + if ($columnType === null) { + return new UnknownType(); + } + + $typeName = str($columnType) + ->before(' ') // strip modifiers from a type name such as `bigint unsigned` + ->before('(') // strip the length from a type name such as `tinyint(4)` + ->toString(); + + if (in_array($typeName, ['int', 'integer', 'tinyint', 'smallint', 'mediumint', 'bigint'])) { + return new IntegerType(); + } + + if ($dbDriverName === 'sqlite' && in_array($typeName, ['float', 'double', 'decimal'])) { + return new FloatType(); + } + + if (in_array($dbDriverName, ['mysql', 'mariadb']) && in_array($typeName, ['float', 'double'])) { + return new FloatType(); + } - return $attributeType; + return new StringType(); } /** diff --git a/src/Support/ResponseExtractor/ModelInfo.php b/src/Support/ResponseExtractor/ModelInfo.php index 4d3ff44a..9faef86b 100644 --- a/src/Support/ResponseExtractor/ModelInfo.php +++ b/src/Support/ResponseExtractor/ModelInfo.php @@ -163,6 +163,7 @@ protected function getAttributes($model) return collect($columns) ->values() ->map(fn ($column) => [ + 'driver' => $connection->getDriverName(), 'name' => $column['name'], 'type' => $column['type'], 'increments' => $column['auto_increment'], @@ -226,6 +227,7 @@ protected function getVirtualAttributes($model, $columns) }) ->reject(fn ($cast, $name) => $keyedColumns->has($name)) ->map(fn ($cast, $name) => [ + 'driver' => null, 'name' => $name, 'type' => null, 'increments' => false, From fa3890c27f2683c753e1149d9fb0dfb81e5b1a81 Mon Sep 17 00:00:00 2001 From: Kyle Date: Fri, 17 May 2024 17:10:36 +0200 Subject: [PATCH 2/2] Add note about uninferred model attribute types --- src/Support/InferExtensions/ModelExtension.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Support/InferExtensions/ModelExtension.php b/src/Support/InferExtensions/ModelExtension.php index 912a75fc..5387f16b 100644 --- a/src/Support/InferExtensions/ModelExtension.php +++ b/src/Support/InferExtensions/ModelExtension.php @@ -56,7 +56,8 @@ public function getPropertyType(PropertyFetchEvent $event): ?Type if ($attribute = $info->get('attributes')->get($event->getName())) { $baseType = $this->getAttributeTypeFromEloquentCasts($attribute['cast'] ?? '') - ?? $this->getAttributeTypeFromDbColumnType($attribute['type'], $attribute['driver']); + ?? $this->getAttributeTypeFromDbColumnType($attribute['type'], $attribute['driver']) + ?? new UnknownType("Virtual attribute ({$attribute['name']}) type inference not supported."); if ($attribute['nullable']) { return Union::wrap([$baseType, new NullType()]); @@ -76,10 +77,10 @@ public function getPropertyType(PropertyFetchEvent $event): ?Type * MySQL/MariaDB decimal is mapped to a string by PDO. * Floating point numbers and decimals are all mapped to strings when using the pgsql driver. */ - private function getAttributeTypeFromDbColumnType(?string $columnType, ?string $dbDriverName): AbstractType + private function getAttributeTypeFromDbColumnType(?string $columnType, ?string $dbDriverName): ?AbstractType { if ($columnType === null) { - return new UnknownType(); + return null; } $typeName = str($columnType)