Skip to content

Commit 7f7fa56

Browse files
leeoptimaclaude
andcommitted
feat: Add enum value detection for model schema
- Detect PHP enum casts and include case values as enum:val1|val2|val3 - Parse SQL ENUM column definitions for native enum values - PHP enum casts take priority over SQL ENUM definitions - Added test coverage for PHP enum detection Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 21954db commit 7f7fa56

File tree

5 files changed

+97
-6
lines changed

5 files changed

+97
-6
lines changed

src/Services/ModelSchemaService.php

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -157,24 +157,88 @@ protected function buildSchema(string $modelClass, int $depth, int $maxDepth, ar
157157
protected function getColumns(Model $model): array
158158
{
159159
$tableName = $this->getTableName($model->getTable());
160+
$casts = $model->getCasts();
160161

161162
return collect($this->getDatabaseColumns($tableName))
163+
->map(fn (array $info, string $column) => $this->applyEnumCast($info, $column, $casts))
162164
->merge($this->getAccessors($model))
163165
->all();
164166
}
165167

168+
/**
169+
* Apply enum information to a column from PHP cast or SQL enum type.
170+
*/
171+
protected function applyEnumCast(array $info, string $column, array $casts): array
172+
{
173+
// First priority: PHP enum cast
174+
if (isset($casts[$column])) {
175+
$castType = $casts[$column];
176+
177+
// Handle enum casts - could be class string or class:EnumClass format
178+
$enumClass = $castType;
179+
if (str_contains($castType, ':')) {
180+
$enumClass = explode(':', $castType)[1] ?? $castType;
181+
}
182+
183+
// Check if it's a backed enum
184+
if (enum_exists($enumClass)) {
185+
$reflection = new \ReflectionEnum($enumClass);
186+
if ($reflection->isBacked()) {
187+
$cases = array_map(
188+
fn ($case) => $case->value,
189+
$enumClass::cases()
190+
);
191+
192+
$info['type'] = 'enum:' . implode('|', $cases);
193+
$info['enum_class'] = $enumClass;
194+
195+
return $info;
196+
}
197+
}
198+
}
199+
200+
// Second priority: SQL ENUM type - parse values from type_raw
201+
if (($info['type'] ?? '') === 'enum' && isset($info['type_raw'])) {
202+
$sqlEnumValues = $this->parseSqlEnumValues($info['type_raw']);
203+
if (! empty($sqlEnumValues)) {
204+
$info['type'] = 'enum:' . implode('|', $sqlEnumValues);
205+
}
206+
}
207+
208+
return $info;
209+
}
210+
211+
/**
212+
* Parse SQL enum values from the raw type definition.
213+
* e.g., "enum('draft','published','archived')" => ['draft', 'published', 'archived']
214+
*
215+
* @return array<string>
216+
*/
217+
protected function parseSqlEnumValues(string $typeRaw): array
218+
{
219+
if (! preg_match("/^enum\s*\((.+)\)$/i", $typeRaw, $matches)) {
220+
return [];
221+
}
222+
223+
// Parse the comma-separated quoted values
224+
preg_match_all("/'([^']+)'/", $matches[1], $valueMatches);
225+
226+
return $valueMatches[1] ?? [];
227+
}
228+
166229
/**
167230
* Get database columns for a table.
168231
*/
169232
protected function getDatabaseColumns(string $tableName): Collection
170233
{
171234
try {
172-
return collect(Schema::getColumnListing($tableName))
173-
->mapWithKeys(fn (string $column) => [
174-
$column => [
175-
'name' => $column,
176-
'type' => $this->getColumnType($tableName, $column),
177-
'type_icon' => $this->getTypeIcon($this->getColumnType($tableName, $column)),
235+
return collect(Schema::getColumns($tableName))
236+
->mapWithKeys(fn (array $column) => [
237+
$column['name'] => [
238+
'name' => $column['name'],
239+
'type' => $column['type_name'],
240+
'type_raw' => $column['type'],
241+
'type_icon' => $this->getTypeIcon($column['type_name']),
178242
'is_accessor' => false,
179243
],
180244
]);

tests/Feature/ModelSchemaServiceTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,13 @@
120120
expect($fields)->toHaveKey('posts.id');
121121
expect($fields)->toHaveKey('posts.title');
122122
});
123+
124+
it('detects enum casts and includes options', function () {
125+
$service = app(ModelSchemaService::class);
126+
127+
$schema = $service->getSchema(Post::class);
128+
129+
expect($schema['columns'])->toHaveKey('status');
130+
expect($schema['columns']['status']['type'])->toBe('enum:draft|published|archived');
131+
expect($schema['columns']['status']['enum_class'])->toBe(\Visualbuilder\EloquentSchema\Tests\Fixtures\Enums\PostStatus::class);
132+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Visualbuilder\EloquentSchema\Tests\Fixtures\Enums;
6+
7+
enum PostStatus: string
8+
{
9+
case DRAFT = 'draft';
10+
case PUBLISHED = 'published';
11+
case ARCHIVED = 'archived';
12+
}

tests/Fixtures/Models/Post.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Illuminate\Database\Eloquent\Relations\BelongsTo;
99
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
1010
use Illuminate\Database\Eloquent\Relations\HasMany;
11+
use Visualbuilder\EloquentSchema\Tests\Fixtures\Enums\PostStatus;
1112

1213
class Post extends Model
1314
{
@@ -16,12 +17,14 @@ class Post extends Model
1617
'title',
1718
'slug',
1819
'content',
20+
'status',
1921
'published_at',
2022
];
2123

2224
protected function casts(): array
2325
{
2426
return [
27+
'status' => PostStatus::class,
2528
'published_at' => 'datetime',
2629
];
2730
}

tests/database/migrations/0001_01_01_000001_create_test_tables.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public function up(): void
1515
$table->string('name');
1616
$table->string('email')->unique();
1717
$table->text('bio')->nullable();
18+
$table->enum('account_type', ['free', 'premium', 'enterprise'])->default('free');
1819
$table->timestamps();
1920
});
2021

@@ -24,6 +25,7 @@ public function up(): void
2425
$table->string('title');
2526
$table->string('slug')->unique();
2627
$table->text('content');
28+
$table->string('status')->default('draft');
2729
$table->timestamp('published_at')->nullable();
2830
$table->timestamps();
2931
});

0 commit comments

Comments
 (0)