Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/Actions/WriteColumnAttribute.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use ReflectionException;
use ReflectionFunction;
use ReflectionNamedType;
use ReflectionUnionType;

class WriteColumnAttribute
{
Expand Down Expand Up @@ -71,6 +72,39 @@ public function __invoke(ReflectionClass $reflectionModel, array $attribute, arr
if ($rt->allowsNull()) {
$attribute['nullable'] = true;
}
} elseif (! is_null($rt) && $rt instanceof ReflectionUnionType) {
$types = [];
$isNullable = false;

foreach ($rt->getTypes() as $unionType) {
if ($unionType instanceof ReflectionNamedType) {
if ($unionType->getName() === 'null') {
$isNullable = true;
continue;
}

$mapped = $returnType($unionType->getName(), $mappings);

// Check for enum types
$ref = $this->resolveEnum($unionType->getName());
if ($ref) {
$mapped = $this->getClassName($unionType->getName());
$enumRef = $ref;
}

if ($mapped !== 'unknown' && ! in_array($mapped, $types)) {
$types[] = $mapped;
}
}
}

if (! empty($types)) {
$type = implode(' | ', $types);
}

if ($isNullable) {
$attribute['nullable'] = true;
}
}
} else {
if ($attribute['type'] !== null) {
Expand Down
35 changes: 35 additions & 0 deletions test/Tests/Feature/Actions/WriteColumnAttributeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ class WriteColumnAttributeTest extends TestCase
'bigint' => 'number',
'bool' => 'boolean',
'boolean' => 'boolean',
'float' => 'number',
'int' => 'number',
'string' => 'string',
];

public function test_action_can_be_resolved_by_application()
Expand All @@ -44,4 +47,36 @@ public function test_action_can_be_executed()
$this->assertArrayHasKey('0', $result);
$this->assertArrayHasKey('1', $result);
}

public function test_union_type_float_int_maps_to_number()
{
$action = app(WriteColumnAttribute::class);
$attribute = array_merge($this->attribute, ['name' => 'score']);
$result = $action(new ReflectionClass(User::class), $attribute, $this->mappings, jsonOutput: true);

$this->assertIsArray($result);
$this->assertEquals('number', $result[0]['type']);
$this->assertNull($result[1]);
}

public function test_nullable_union_type_float_int_null()
{
$action = app(WriteColumnAttribute::class);
$attribute = array_merge($this->attribute, ['name' => 'score_nullable']);
$result = $action(new ReflectionClass(User::class), $attribute, $this->mappings, jsonOutput: true);

$this->assertIsArray($result);
$this->assertEquals('number | null', $result[0]['type']);
}

public function test_union_type_with_enum()
{
$action = app(WriteColumnAttribute::class);
$attribute = array_merge($this->attribute, ['name' => 'role_or_string']);
$result = $action(new ReflectionClass(User::class), $attribute, $this->mappings, jsonOutput: true);

$this->assertIsArray($result);
$this->assertEquals('Roles | string', $result[0]['type']);
$this->assertNotNull($result[1]);
}
}
3 changes: 3 additions & 0 deletions test/input/expectations/user-api-resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export interface User {
role_new: string
role_enum: Roles
role_enum_traditional: Roles
score: number
score_nullable: number | null
role_or_string: Roles | string
// relations
notifications: DatabaseNotification[]
// counts
Expand Down
3 changes: 3 additions & 0 deletions test/input/expectations/user-enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export interface User {
role_new: string
role_enum: RolesEnum
role_enum_traditional: RolesEnum
score: number
score_nullable: number | null
role_or_string: Roles | stringEnum
// relations
notifications: DatabaseNotification[]
// counts
Expand Down
3 changes: 3 additions & 0 deletions test/input/expectations/user-fillables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export interface User {
role_new: string
role_enum: Roles
role_enum_traditional: Roles
score: number
score_nullable: number | null
role_or_string: Roles | string
// relations
notifications: DatabaseNotification[]
// counts
Expand Down
3 changes: 3 additions & 0 deletions test/input/expectations/user-global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ declare global {
role_new: string
role_enum: Roles
role_enum_traditional: Roles
score: number
score_nullable: number | null
role_or_string: Roles | string
// relations
notifications: DatabaseNotification[]
// counts
Expand Down
3 changes: 3 additions & 0 deletions test/input/expectations/user-no-hidden.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ export interface User {
role_new: string
role_enum: Roles
role_enum_traditional: Roles
score: number
score_nullable: number | null
role_or_string: Roles | string
// relations
notifications: DatabaseNotification[]
// counts
Expand Down
3 changes: 3 additions & 0 deletions test/input/expectations/user-no-relations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export interface User {
role_new: string
role_enum: Roles
role_enum_traditional: Roles
score: number
score_nullable: number | null
role_or_string: Roles | string
// counts
notifications_count: number
// exists
Expand Down
3 changes: 3 additions & 0 deletions test/input/expectations/user-optional-nullables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export interface User {
role_new: string
role_enum: Roles
role_enum_traditional: Roles
score: number
score_nullable?: number | null
role_or_string: Roles | string
// relations
notifications: DatabaseNotification[]
// counts
Expand Down
3 changes: 3 additions & 0 deletions test/input/expectations/user-optional-relations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export interface User {
role_new: string
role_enum: Roles
role_enum_traditional: Roles
score: number
score_nullable: number | null
role_or_string: Roles | string
// relations
notifications?: DatabaseNotification[]
// counts
Expand Down
3 changes: 3 additions & 0 deletions test/input/expectations/user-plurals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export interface User {
role_new: string
role_enum: Roles
role_enum_traditional: Roles
score: number
score_nullable: number | null
role_or_string: Roles | string
// relations
notifications: DatabaseNotifications
// counts
Expand Down
3 changes: 3 additions & 0 deletions test/input/expectations/user-timestamps-date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export interface User {
role_new: string
role_enum: Roles
role_enum_traditional: Roles
score: number
score_nullable: number | null
role_or_string: Roles | string
// relations
notifications: DatabaseNotification[]
// counts
Expand Down
3 changes: 3 additions & 0 deletions test/input/expectations/user-types-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export type User = {
role_new: string
role_enum: Roles
role_enum_traditional: Roles
score: number
score_nullable: number | null
role_or_string: Roles | string
// relations
notifications: DatabaseNotification[]
// counts
Expand Down
3 changes: 3 additions & 0 deletions test/input/expectations/user-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export type User = {
role_new: string
role_enum: Roles
role_enum_traditional: Roles
score: number
score_nullable: number | null
role_or_string: Roles | string
// relations
notifications: DatabaseNotification[]
// counts
Expand Down
18 changes: 18 additions & 0 deletions test/input/expectations/user.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,18 @@
"name": "role_enum_traditional",
"type": "RolesEnum"
},
{
"name": "score",
"type": "number"
},
{
"name": "score_nullable",
"type": "number | null"
},
{
"name": "role_or_string",
"type": "Roles | stringEnum"
},
{
"name": "notifications_count",
"type": "number"
Expand All @@ -74,6 +86,12 @@
"type": "export const enum Roles {\n \/** Can do anything *\/\n ADMIN = 'admin',\n \/** Standard readonly *\/\n USER = 'user',\n \/** Value that needs string escaping *\/\n USERCLASS = 'App\\\\Models\\\\User',\n}\n\nexport type RolesEnum = `${Roles}`\n\n"
}
},
{
"Roles": {
"name": "Roles",
"type": "export const enum Roles {\n \/** Can do anything *\/\n ADMIN = 'admin',\n \/** Standard readonly *\/\n USER = 'user',\n \/** Value that needs string escaping *\/\n USERCLASS = 'App\\\\Models\\\\User',\n}\n\nexport type RolesEnum = `${Roles}`\n\n"
}
},
{
"Roles": {
"name": "Roles",
Expand Down
3 changes: 3 additions & 0 deletions test/input/expectations/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export interface User {
role_new: string
role_enum: Roles
role_enum_traditional: Roles
score: number
score_nullable: number | null
role_or_string: Roles | string
// relations
notifications: DatabaseNotification[]
// counts
Expand Down
24 changes: 24 additions & 0 deletions test/laravel-skeleton/app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,28 @@ public function getRoleEnumTraditionalAttribute(): Roles
{
return Roles::ADMIN;
}

/**
* Get the user's score as a union type using Laravel's Attribute accessor.
*/
protected function score(): Attribute
{
return Attribute::get(fn (): float|int => 42);
}

/**
* Get the user's score as a nullable union type using Laravel's Attribute accessor.
*/
protected function scoreNullable(): Attribute
{
return Attribute::get(fn (): float|int|null => null);
}

/**
* Get the user's role as a union type with an enum using Laravel's Attribute accessor.
*/
protected function roleOrString(): Attribute
{
return Attribute::get(fn (): Roles|string => 'admin');
}
}